Browse Source

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

mo 2 years ago
parent
commit
bbe0ff0723
99 changed files with 2253 additions and 529 deletions
  1. BIN
      src/common/images/404.png
  2. 6 0
      src/components/o-empty/index.module.less
  3. 11 2
      src/components/o-empty/index.tsx
  4. 1 1
      src/components/o-sticky/index.module.less
  5. 6 6
      src/helpers/native-message.ts
  6. 3 3
      src/router/index.ts
  7. 16 0
      src/router/routes-common.ts
  8. 8 1
      src/router/routes-student.ts
  9. 3 3
      src/school/attendance/components/attend-student.tsx
  10. 9 0
      src/school/companion-teacher/companion-detail.module.less
  11. 20 18
      src/school/companion-teacher/companion-detail.tsx
  12. 10 15
      src/school/companion-teacher/companion-teacher-register.tsx
  13. 2 2
      src/school/companion-teacher/compontent/teacher.tsx
  14. 4 4
      src/school/companion-teacher/index.tsx
  15. 1 1
      src/school/manage-teacher/index.tsx
  16. 9 0
      src/school/manage-teacher/manage-detail.module.less
  17. 21 15
      src/school/manage-teacher/manage-detail.tsx
  18. 9 6
      src/school/manage-teacher/manage-teacher-register.tsx
  19. 1 1
      src/school/mass-message/component/class-list/index.tsx
  20. 1 1
      src/school/mass-message/component/manage-list/index.tsx
  21. 1 1
      src/school/mass-message/component/student-list/index.tsx
  22. 1 1
      src/school/mass-message/component/teacher-list/teacher-list.tsx
  23. 1 1
      src/school/mass-message/index.tsx
  24. 19 21
      src/school/orchestra/compontent/information.tsx
  25. 1 1
      src/school/orchestra/compontent/photo-detail.tsx
  26. 1 1
      src/school/orchestra/compontent/photo.tsx
  27. 1 1
      src/school/orchestra/compontent/plan.tsx
  28. 4 4
      src/school/orchestra/create-orchestra/index.tsx
  29. 1 1
      src/school/orchestra/index.tsx
  30. 1 1
      src/school/orchestra/modal/student-list.tsx
  31. 5 5
      src/school/orchestra/modal/subject-list.tsx
  32. 1 1
      src/school/orchestra/modal/teacher-list.tsx
  33. 1 1
      src/school/orchestra/orchestra-information.tsx
  34. 3 3
      src/school/ranking-list/components/day-bang.tsx
  35. 3 3
      src/school/ranking-list/components/timer-bang.tsx
  36. 8 0
      src/school/train-planning/component/course-preview/index.module.less
  37. 30 19
      src/school/train-planning/component/course-preview/index.tsx
  38. 24 24
      src/school/train-planning/component/practice/index.tsx
  39. 1 1
      src/school/train-planning/component/train-content/index.tsx
  40. 2 0
      src/school/train-planning/modal/class-list/index.module.less
  41. 4 4
      src/school/train-planning/modal/class-list/index.tsx
  42. 4 1
      src/school/train-planning/modal/practice-class/index.module.less
  43. 20 3
      src/school/train-planning/modal/practice-class/index.tsx
  44. 1 1
      src/student/coupons/list.tsx
  45. 112 0
      src/student/download/transfer.tsx
  46. 1 0
      src/student/music-group/layout/auth.tsx
  47. 30 17
      src/student/music-group/layout/index.module.less
  48. 36 29
      src/student/music-group/layout/login.tsx
  49. 19 15
      src/student/music-group/pre-apply/component/apply.tsx
  50. 1 1
      src/student/music-group/pre-apply/component/order.tsx
  51. BIN
      src/student/music-group/pre-apply/images/banner.png
  52. BIN
      src/student/music-group/pre-apply/images/wx-no-bg.png
  53. BIN
      src/student/music-group/pre-apply/images/wx-no-top.png
  54. 16 4
      src/student/music-group/pre-apply/index.module.less
  55. 98 45
      src/student/music-group/pre-apply/index.tsx
  56. 29 5
      src/student/music-group/pre-apply/order-detail.tsx
  57. 2 2
      src/student/music-group/shop-address/address-operation.tsx
  58. 1 1
      src/student/music-group/shop-address/index.tsx
  59. 2 2
      src/student/my-orchestra/index.tsx
  60. 1 1
      src/student/my-orchestra/photo-detail.tsx
  61. 1 1
      src/student/trade-record/component/paid-list.tsx
  62. 1 1
      src/student/trade-record/component/refund-list.tsx
  63. 1 1
      src/student/trade-record/component/wait-pay.tsx
  64. 1 1
      src/styles/index.less
  65. 34 35
      src/teacher/attendance/index.tsx
  66. 0 1
      src/teacher/screen-projection/index.tsx
  67. 4 4
      src/views/accompany/music-list.tsx
  68. 1 1
      src/views/adapay/use-coupons/choice-coupon.tsx
  69. BIN
      src/views/courseList/image/icon-course.png
  70. 1 1
      src/views/courseList/index.module.less
  71. 115 28
      src/views/courseList/index.tsx
  72. 13 2
      src/views/coursewarePlay/component/musicScore.tsx
  73. 21 1
      src/views/coursewarePlay/component/point.module.less
  74. 33 11
      src/views/coursewarePlay/component/points.tsx
  75. 22 0
      src/views/coursewarePlay/image/icon-down.svg
  76. 22 0
      src/views/coursewarePlay/image/icon-up.svg
  77. BIN
      src/views/coursewarePlay/image/icon-videobg.png
  78. 1 0
      src/views/coursewarePlay/image/icon-zhibo.svg
  79. 0 1
      src/views/coursewarePlay/index.module.less
  80. 168 70
      src/views/coursewarePlay/index.tsx
  81. 184 2
      src/views/exercise-after-class/index.module.less
  82. 400 60
      src/views/exercise-after-class/index.tsx
  83. 3 3
      src/views/exercise-record/index.tsx
  84. 1 1
      src/views/information/help-center/index.tsx
  85. 1 0
      src/views/layout/auth.tsx
  86. 3 3
      src/views/layout/login.tsx
  87. 4 1
      src/views/lessonCourseware/index.tsx
  88. BIN
      src/views/unit-test/images/icon-bell.png
  89. BIN
      src/views/unit-test/images/icon-button-list.png
  90. BIN
      src/views/unit-test/images/icon-count-down.png
  91. BIN
      src/views/unit-test/images/icon-edit.png
  92. BIN
      src/views/unit-test/images/icon-question-nums.png
  93. BIN
      src/views/unit-test/images/icon-timer.png
  94. 68 0
      src/views/unit-test/index.module.less
  95. 168 0
      src/views/unit-test/index.tsx
  96. 117 0
      src/views/unit-test/model/notice-start/index.module.less
  97. 82 0
      src/views/unit-test/model/notice-start/index.tsx
  98. 63 0
      src/views/unit-test/unit-detail/index.module.less
  99. 93 0
      src/views/unit-test/unit-detail/index.tsx

BIN
src/common/images/404.png


+ 6 - 0
src/components/o-empty/index.module.less

@@ -11,6 +11,12 @@
     width: 55%;
     margin: 0 auto;
   }
+  :global {
+    .van-empty__image {
+      width: 210px;
+      height: 210px;
+    }
+  }
   .SMALL {
     :global {
       .van-empty__image {

+ 11 - 2
src/components/o-empty/index.tsx

@@ -27,13 +27,17 @@ export default defineComponent({
       type: String as PropType<'CERT' | 'SMALL'>,
       default: ''
     },
+    imageSize: {
+      type: Number,
+      default: 0
+    },
     plain: {
       type: Boolean,
       default: false
     },
     btnStatus: {
       type: Boolean,
-      default: true
+      default: false
     },
     buttonText: {
       type: String,
@@ -79,7 +83,12 @@ export default defineComponent({
   render() {
     return (
       <div class={[styles['col-result'], 'col-result-container']}>
-        <Empty image={this.image} class={styles[this.classImgSize]} description={this.tips} />
+        <Empty
+          image={this.image}
+          imageSize={this.imageSize || ''}
+          class={styles[this.classImgSize]}
+          description={this.tips}
+        />
 
         {this.btnStatus ? (
           <Button

+ 1 - 1
src/components/o-sticky/index.module.less

@@ -7,7 +7,7 @@
 .white {
   background-color: #fff;
   > div {
-    padding: 15px;
+    padding-top: 15px;
     box-shadow: 0px 0px 10px 0px rgba(216, 216, 216, 0.5);
   }
 }

+ 6 - 6
src/helpers/native-message.ts

@@ -36,14 +36,14 @@ const browserInfo = browser()
 if (browserInfo.isApp) {
   window.addEventListener('message', evt => {
     try {
-      console.log(evt, 'message', evt.data)
+      console.log('message', evt.data)
       const data = evt.data
         ? typeof evt.data === 'object'
           ? evt.data
           : JSON.parse(evt.data)
         : {}
       const uuid = data.content?.uuid || data.uuid
-      console.log(uuid, data.content, 'uuid')
+      // console.log(uuid, data.content, 'uuid')
       try {
         if (data.content) {
           data.content = JSON.parse(data.content)
@@ -52,14 +52,14 @@ if (browserInfo.isApp) {
         //
       }
       if (data?.content?.uuid) {
-        console.log('data', data)
+        // console.log('data', data)
       }
       if (!uuid) {
         const keys = Object.keys(calls).filter(
           key => key.indexOf(data.api) === 0
         )
-        console.log(keys, 'keys')
-        console.log(data, 'data')
+        // console.log(keys, 'keys')
+        // console.log(data, 'data')
         for (const key of keys) {
           const callback = calls[key] || loop
           typeof callback === 'function' && callback(data)
@@ -68,7 +68,7 @@ if (browserInfo.isApp) {
       }
       const callId = data.content?.uuid || data.uuid || data.api + data.uuid
       const callback = calls[callId] || loop
-      console.log(data, 'data')
+      // console.log(data, 'data')
       typeof callback === 'function' && callback(data)
     } catch (error) {
       console.error('通信消息解析错误', error)

+ 3 - 3
src/router/index.ts

@@ -1,5 +1,5 @@
 import { browser } from '@/helpers/utils'
-import { Dialog } from 'vant'
+import { showDialog } from 'vant'
 import { createRouter, createWebHashHistory, createWebHistory, Router } from 'vue-router'
 import { postMessage } from '@/helpers/native-message'
 import routesTeacher from './routes-teacher'
@@ -52,9 +52,9 @@ router.onError((error) => {
     console.log(error)
     if (isChunkLoadFailed && !isOpen) {
       isOpen = true
-      Dialog.alert({
+      showDialog({
         title: '更新提示',
-        message: 'APP有更新请点击确定刷新页面?',
+        message: 'APP有更新请点击确定刷新页面',
         confirmButtonColor: 'var(--van-primary)'
       }).then(() => {
         // on close

+ 16 - 0
src/router/routes-common.ts

@@ -78,6 +78,22 @@ export const router: RouteRecordRaw[] = [
     meta: {
       title: '帮助中心'
     }
+  },
+  {
+    path: '/unit-test',
+    name: 'unit-test',
+    component: () => import('@/views/unit-test'),
+    meta: {
+      title: '单元测验'
+    }
+  },
+  {
+    path: '/unit-detail',
+    name: 'unit-detail',
+    component: () => import('@/views/unit-test/unit-detail'),
+    meta: {
+      title: '测验详情'
+    }
   }
 ]
 

+ 8 - 1
src/router/routes-student.ts

@@ -22,7 +22,14 @@ const noLoginRouter = [
     meta: {
       title: '下载管乐团学生端'
     }
-  }
+  },
+  {
+    path: '/transfer',
+    component: () => import('@/student/download/transfer'),
+    meta: {
+      title: '管乐团'
+    }
+  },
 ]
 
 export default [

+ 3 - 3
src/school/attendance/components/attend-student.tsx

@@ -135,13 +135,13 @@ export default defineComponent({
 
     const getSubjects = async () => {
       try {
-        const res = await request.post('/api-school/subject/page', {
+        const res = await request.post('/api-school/subjectBasicConfig/page', {
           data: { page: 1, rows: 9999 }
         })
         state.subjects = res.data.rows.map((item) => {
           return {
-            name: item.name,
-            value: item.id as string
+            name: item.subjectName,
+            value: item.subjectId as string
           }
         })
         state.subjects.unshift({ name: '全部声部', value: '' })

+ 9 - 0
src/school/companion-teacher/companion-detail.module.less

@@ -13,6 +13,14 @@
     margin-right: 10px;
   }
 }
+.sectionCellGroup {
+  :global {
+    .van-cell {
+      padding: 18px 15px;
+      font-size: 16px;
+    }
+  }
+}
 
 .detailCell {
   padding: 15px 13px;
@@ -153,6 +161,7 @@
     line-height: 22px;
   }
   .musicName {
+    padding-top: 6px;
     font-size: 14px;
     color: #777777;
     line-height: 20px;

+ 20 - 18
src/school/companion-teacher/companion-detail.tsx

@@ -137,23 +137,25 @@ export default defineComponent({
               )
             }}
           </Cell>
-          <Cell center>
-            {{
-              title: () => (
-                <div class={styles.subjectContainer}>
-                  <span>声部:</span>
-                  <div style={{ display: 'flex', alignItems: 'center' }}>
-                    {state.detail.subjectNames &&
-                      state.detail.subjectNames.map((subject: any) => (
-                        <Tag type="primary" class={styles.tagSubject}>
-                          {subject}
-                        </Tag>
-                      ))}
+          {state.detail.subjectNames && state.detail.subjectNames.legnth > 0 && (
+            <Cell center>
+              {{
+                title: () => (
+                  <div class={styles.subjectContainer}>
+                    <span>声部:</span>
+                    <div style={{ display: 'flex', alignItems: 'center' }}>
+                      {state.detail.subjectNames &&
+                        state.detail.subjectNames.map((subject: any) => (
+                          <Tag type="primary" class={styles.tagSubject}>
+                            {subject}
+                          </Tag>
+                        ))}
+                    </div>
                   </div>
-                </div>
-              )
-            }}
-          </Cell>
+                )
+              }}
+            </Cell>
+          )}
         </CellGroup>
 
         <div class={styles.sectionTitle}>
@@ -161,7 +163,7 @@ export default defineComponent({
           基本信息
         </div>
 
-        <CellGroup inset class={styles.detailCellGroup}>
+        <CellGroup inset class={[styles.detailCellGroup, styles.sectionCellGroup]}>
           <Cell title="手机号码" value={state.detail.phone}></Cell>
           <Cell
             title="性别"
@@ -174,7 +176,7 @@ export default defineComponent({
           <i></i>
           所在班级
         </div>
-        <CellGroup inset class={styles.detailCellGroup}>
+        <CellGroup inset class={[styles.detailCellGroup, styles.sectionCellGroup]}>
           {state.classList.map((item: any) => (
             <Cell center class={styles.companionCell}>
               {{

+ 10 - 15
src/school/companion-teacher/companion-teacher-register.tsx

@@ -197,19 +197,22 @@ export default defineComponent({
         })
         state.columns = tempareas || []
 
-        const { data } = await request.post('/api-school/open/subject/page', {
-          data: {
-            page: 1,
-            rows: 50
+        const { data } = await request.post(
+          '/api-school/open/orchestraSubjectConfig/pageByOrchestraId',
+          {
+            data: {
+              page: 1,
+              rows: 50
+            }
           }
-        })
+        )
 
         const rows = data.rows || []
         const tempSubjects: any = []
-        rows.forEach((item) => {
+        rows.forEach((item: any) => {
           tempSubjects.push({
             text: item.name,
-            value: item.id
+            value: item.subjectId
           })
         })
         state.columnSubject = tempSubjects
@@ -522,14 +525,6 @@ export default defineComponent({
             onConfirm={onConfirmSubject}
           />
         </Popup>
-        <Popup v-model:show={state.showSubject} position="bottom" round>
-          <Picker
-            showToolbar
-            columns={state.columnSubject}
-            onCancel={() => (state.showSubject = false)}
-            onConfirm={onConfirmSubject}
-          />
-        </Popup>
         {/* 学历 */}
         {/* 学历分为专科、本科、硕士、博士、其他 */}
         <Popup v-model:show={state.showEducation} position="bottom" round>

+ 2 - 2
src/school/companion-teacher/compontent/teacher.tsx

@@ -90,7 +90,7 @@ export default defineComponent({
     return () => (
       <>
         <OSticky position="top">
-          <OHeader title="交接老师" />
+          <OHeader title="交接老师" desotry={false} />
           <OSearch
             placeholder="请输入老师姓名"
             inputBackground="white"
@@ -132,7 +132,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无伴学老师" />
+          <OEmpty btnStatus={false} tips="暂无伴学老师" />
         )}
       </>
     )

+ 4 - 4
src/school/companion-teacher/index.tsx

@@ -70,7 +70,7 @@ export default defineComponent({
 
     const getSubjects = async () => {
       try {
-        const { data } = await request.post('/api-school/subject/page', {
+        const { data } = await request.post('/api-school/subjectBasicConfig/page', {
           data: {
             page: 1,
             rows: 50
@@ -80,8 +80,8 @@ export default defineComponent({
         const temp = data.rows || []
         temp.forEach((row: any) => {
           form.subjectList.push({
-            text: row.name,
-            value: row.id
+            text: row.subjectName,
+            value: row.subjectId
           })
         })
       } catch {
@@ -372,7 +372,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无伴学老师" />
+          <OEmpty btnStatus={false} tips="暂无伴学老师" />
         )}
 
         <Popup

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

@@ -290,7 +290,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无管理老师" />
+          <OEmpty btnStatus={false} tips="暂无管理老师" />
         )}
 
         <Popup

+ 9 - 0
src/school/manage-teacher/manage-detail.module.less

@@ -4,6 +4,15 @@
   border-radius: 10px;
 }
 
+.sectionCellGroup {
+  :global {
+    .van-cell {
+      padding: 18px 15px;
+      font-size: 16px;
+    }
+  }
+}
+
 .detailCell {
   padding: 15px 13px;
 

+ 21 - 15
src/school/manage-teacher/manage-detail.tsx

@@ -7,6 +7,7 @@ import { useRoute } from 'vue-router'
 import styles from './manage-detail.module.less'
 import MenuFunction from './menu-function'
 import iconTeacher from '@common/images/icon_teacher.png'
+import OSticky from '@/components/o-sticky'
 
 export default defineComponent({
   name: 'manage-detail',
@@ -84,7 +85,7 @@ export default defineComponent({
           基本信息
         </div>
 
-        <CellGroup inset class={styles.detailCellGroup}>
+        <CellGroup inset class={[styles.detailCellGroup, styles.sectionCellGroup]}>
           <Cell title={'手机号码'} value={detail.value.nickname}></Cell>
           <Cell
             title={'性别'}
@@ -114,21 +115,26 @@ export default defineComponent({
           </>
         )}
 
-        <div class={['btnGroup', 'btnMore']} style={{ paddingLeft: '13px', paddingRight: '13px' }}>
-          <Button type="primary" round onClick={() => (state.menuStatus = true)}>
-            修改权限
-          </Button>
-          <Button
-            type="primary"
-            round
-            color="#64A9FF"
-            onClick={() => (state.status = true)}
-            // disabled={ ? false : true}
+        <OSticky position="bottom">
+          <div
+            class={['btnGroup', 'btnMore']}
+            style={{ paddingLeft: '13px', paddingRight: '13px' }}
           >
-            {detail.value.status === 'ACTIVATION' && '冻结账号'}
-            {detail.value.status === 'LOCKED' && '解冻账号'}
-          </Button>
-        </div>
+            <Button type="primary" round onClick={() => (state.menuStatus = true)}>
+              修改权限
+            </Button>
+            <Button
+              type="primary"
+              round
+              color="#64A9FF"
+              onClick={() => (state.status = true)}
+              // disabled={ ? false : true}
+            >
+              {detail.value.status === 'ACTIVATION' && '冻结账号'}
+              {detail.value.status === 'LOCKED' && '解冻账号'}
+            </Button>
+          </div>
+        </OSticky>
 
         <OPopup v-model:modelValue={state.menuStatus} destroy>
           <MenuFunction

+ 9 - 6
src/school/manage-teacher/manage-teacher-register.tsx

@@ -126,19 +126,22 @@ export default defineComponent({
         })
         state.columns = tempareas || []
 
-        const { data } = await request.post('/api-school/open/subject/page', {
-          data: {
-            page: 1,
-            rows: 50
+        const { data } = await request.post(
+          '/api-school/open/orchestraSubjectConfig/pageByOrchestraId',
+          {
+            data: {
+              page: 1,
+              rows: 50
+            }
           }
-        })
+        )
 
         const rows = data.rows || []
         const tempSubjects: any = []
         rows.forEach((item) => {
           tempSubjects.push({
             text: item.name,
-            value: item.id
+            value: item.subjectId
           })
         })
         state.columnSubject = tempSubjects

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

@@ -244,7 +244,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无班级" />
+          <OEmpty btnStatus={false} tips="暂无班级" />
         )}
 
         <Popup v-model:show={forms.showPopover} position="bottom" round>

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

@@ -196,7 +196,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+          <OEmpty btnStatus={false} tips="暂无老师" />
         )}
       </div>
     )

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

@@ -192,7 +192,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+          <OEmpty btnStatus={false} tips="暂无老师" />
         )}
       </div>
     )

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

@@ -214,7 +214,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+          <OEmpty btnStatus={false} tips="暂无老师" />
         )}
       </div>
     )

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

@@ -156,7 +156,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无群发消息" />
+          <OEmpty btnStatus={false} tips="暂无群发消息" />
         )}
       </div>
     )

+ 19 - 21
src/school/orchestra/compontent/information.tsx

@@ -398,30 +398,28 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无班级" />
+          <OEmpty btnStatus={false} tips="暂无班级" />
         )}
 
         {/*  */}
-        {(state.orchestraInfo.deliveryType === 'SINGLE_DELIVERY' ||
-          state.orchestraInfo.deliveryType === 'MULTIPLE_DELIVERY') &&
-          ['REGISTER', 'DOING', 'DONE'].includes(state.orchestraInfo.status) && (
-            <OSticky position="bottom">
-              <div class={'btnGroup'}>
-                <Button
-                  round
-                  block
-                  type="primary"
-                  onClick={() => {
-                    state.showQrcode = true
-                    state.qrcodeUrl =
-                      window.location.origin + '/orchestra-student/#/preApply?id=' + route.query.id
-                  }}
-                >
-                  报名二维码
-                </Button>
-              </div>
-            </OSticky>
-          )}
+        {state.orchestraInfo.canSignUp && (
+          <OSticky position="bottom">
+            <div class={'btnGroup'}>
+              <Button
+                round
+                block
+                type="primary"
+                onClick={() => {
+                  state.showQrcode = true
+                  state.qrcodeUrl =
+                    window.location.origin + '/orchestra-student/#/preApply?id=' + route.query.id
+                }}
+              >
+                报名二维码
+              </Button>
+            </div>
+          </OSticky>
+        )}
 
         <Popup
           v-model:show={state.showQrcode}

+ 1 - 1
src/school/orchestra/compontent/photo-detail.tsx

@@ -324,7 +324,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无相片" />
+          <OEmpty btnStatus={false} tips="暂无相片" />
         )}
 
         {state.isEdit && (

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

@@ -223,7 +223,7 @@ export default defineComponent({
             </div>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无相册" />
+          <OEmpty btnStatus={false} tips="暂无相册" />
         )}
 
         <Popup v-model:show={state.status} round style={{ width: '80%' }}>

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

@@ -232,7 +232,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无班级" />
+          <OEmpty btnStatus={false} tips="暂无班级" />
         )}
 
         <Popup v-model:show={state.timeShow} position="bottom" round>

+ 4 - 4
src/school/orchestra/create-orchestra/index.tsx

@@ -29,7 +29,7 @@ export default defineComponent({
     // 获取声部
     const getSubjects = async () => {
       try {
-        const { data } = await request.post('/api-school/subject/page', {
+        const { data } = await request.post('/api-school/subjectBasicConfig/page', {
           data: {
             page: 1,
             rows: 50
@@ -75,8 +75,8 @@ export default defineComponent({
           // 判断是否在数据里,如果在则直接添加,不能重置数据
           if (index < 0) {
             temps.push({
-              id: item.id,
-              name: item.name,
+              id: item.subjectId,
+              name: item.subjectName,
               code: item.code,
               type: null,
               teacher: {}, // 老师信息
@@ -119,8 +119,8 @@ export default defineComponent({
       let largeUpSubject: any = {} // 上低音号和大号合集,目前根据编码处理,待定
       state.selectSubjects.forEach((item: any) => {
         console.log(item, 'item.name')
+        tempStudents.push(...item.students)
         if (item.code !== 'BARITONE' && item.code !== 'TUBA') {
-          tempStudents.push(...item.students)
           tempSelect.push(item)
         } else {
           // 获取学生

+ 1 - 1
src/school/orchestra/index.tsx

@@ -166,7 +166,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无乐团" />
+          <OEmpty btnStatus={false} tips="暂无乐团" />
         )}
       </>
     )

+ 1 - 1
src/school/orchestra/modal/student-list.tsx

@@ -249,7 +249,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无学生" />
+          <OEmpty btnStatus={false} tips="暂无学生" />
         )}
 
         <OSticky position="bottom">

+ 5 - 5
src/school/orchestra/modal/subject-list.tsx

@@ -55,11 +55,11 @@ export default defineComponent({
           style={{ paddingBottom: '24px' }}
         >
           {props.subjectList.map((item: any) => (
-            <div class={styles.subject} onClick={() => onSelect(item.id)}>
+            <div class={styles.subject} onClick={() => onSelect(item.subjectId)}>
               <Checkbox
-                name={item.id}
+                name={item.subjectId}
                 class={styles.checkbox}
-                ref={(el: any) => (state.checkboxRefs[item.id] = el)}
+                ref={(el: any) => (state.checkboxRefs[item.subjectId] = el)}
                 onClick={(e: any) => {
                   e.stopPropagation()
                 }}
@@ -72,8 +72,8 @@ export default defineComponent({
                   )
                 }}
               />
-              <Image class={styles.img} src={item.img} />
-              <p class={styles.name}>{item.name}</p>
+              <Image class={styles.img} src={item.subjectImg} />
+              <p class={styles.name}>{item.subjectName}</p>
             </div>
           ))}
         </CheckboxGroup>

+ 1 - 1
src/school/orchestra/modal/teacher-list.tsx

@@ -167,7 +167,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+          <OEmpty btnStatus={false} tips="暂无老师" />
         )}
       </div>
     )

+ 1 - 1
src/school/orchestra/orchestra-information.tsx

@@ -187,7 +187,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无资讯" />
+          <OEmpty btnStatus={false} tips="暂无资讯" />
         )}
 
         <OPopup v-model:modelValue={state.addStatus} style={{ background: '#f8f8f8' }} destroy>

+ 3 - 3
src/school/ranking-list/components/day-bang.tsx

@@ -142,13 +142,13 @@ export default defineComponent({
 
     const getSubjects = async () => {
       try {
-        const res = await request.post('/api-school/subject/page', {
+        const res = await request.post('/api-school/subjectBasicConfig/page', {
           data: { page: 1, rows: 9999 }
         })
         state.subjects = res.data.rows.map((item) => {
           return {
-            name: item.name,
-            value: item.id as string
+            name: item.subjectName,
+            value: item.subjectId as string
           }
         })
         state.subjects.unshift({ name: '全部声部', value: '' })

+ 3 - 3
src/school/ranking-list/components/timer-bang.tsx

@@ -142,13 +142,13 @@ export default defineComponent({
 
     const getSubjects = async () => {
       try {
-        const res = await request.post('/api-school/subject/page', {
+        const res = await request.post('/api-school/subjectBasicConfig/page', {
           data: { page: 1, rows: 9999 }
         })
         state.subjects = res.data.rows.map((item) => {
           return {
-            name: item.name,
-            value: item.id as string
+            name: item.subjectName,
+            value: item.subjectId as string
           }
         })
         state.subjects.unshift({ name: '全部声部', value: '' })

+ 8 - 0
src/school/train-planning/component/course-preview/index.module.less

@@ -31,6 +31,9 @@
   }
 }
 
+.courseTabsContainer {
+  height: 50px;
+}
 .courseTabs {
   :global {
     .van-tabs__line {
@@ -67,6 +70,11 @@
   }
   .cellTeacher {
     padding: 10px 12px 15px;
+    :global {
+      .van-button {
+        margin: 4px 0;
+      }
+    }
   }
   .conflictGrouop {
     padding: 0 12px;

+ 30 - 19
src/school/train-planning/component/course-preview/index.tsx

@@ -1,7 +1,7 @@
 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 { defineComponent, onMounted, reactive, ref } from 'vue'
+import { defineComponent, onMounted, reactive, ref, nextTick } from 'vue'
 import styles from './index.module.less'
 import iconTimer from '../../images/icon-timer.png'
 import iconTeacher from '@common/images/icon_teacher.png'
@@ -50,18 +50,24 @@ export default defineComponent({
             ? { orchestraId: forms.selectOrchestraId }
             : forms.planList.orchestra[0]
           state.tabValue = selectOrchestra.orchestraId
+          console.log(forms.selectClassGroupId, forms.planList.classes[selectOrchestra.orchestraId])
           const selectClasses = forms.selectClassGroupId
             ? { classGroupId: forms.selectClassGroupId }
             : forms.planList.classes[selectOrchestra.orchestraId]
             ? forms.planList.classes[selectOrchestra.orchestraId][0]
             : {}
-          state.courseValue = selectClasses.classGroupId
           state.selectClasses = forms.planList.classes[selectOrchestra.orchestraId] || []
           state.selectCourse = forms.planList.course[selectClasses.classGroupId]
 
+          state.courseValue = selectClasses.classGroupId
+
+          console.log(selectClasses.classGroupId, 'selectClasses.classGroupId')
+
           // 判断是否有数据
           forms.selectOrchestraId = null
           forms.selectClassGroupId = null
+
+          console.log(selectClasses.classGroupId, 'selectClasses.classGroupId 333333')
         }
       } catch {
         //
@@ -165,6 +171,9 @@ export default defineComponent({
       } catch {
         //
         state.isClick = false
+        setTimeout(() => {
+          getClasses()
+        }, 1100)
       }
     }
 
@@ -201,23 +210,25 @@ export default defineComponent({
             ))}
           </Tabs>
 
-          {/* {state.courseValue && ( */}
-          <Tabs
-            swipeThreshold={3}
-            class={styles.courseTabs}
-            v-model:active={state.courseValue}
-            lineHeight={0}
-            shrink
-            ref={courseTabsRef}
-            onChange={(val: any) => {
-              state.selectCourse = forms.planList.course[val]
-            }}
-          >
-            {state.selectClasses.map((item: any) => (
-              <Tab title={item.className} name={item.classGroupId}></Tab>
-            ))}
-          </Tabs>
-          {/* )} */}
+          <div class={styles.courseTabsContainer}>
+            {state.courseValue && (
+              <Tabs
+                swipeThreshold={3}
+                class={styles.courseTabs}
+                v-model:active={state.courseValue}
+                lineHeight={0}
+                shrink
+                ref={courseTabsRef}
+                onChange={(val: any) => {
+                  state.selectCourse = forms.planList.course[val]
+                }}
+              >
+                {state.selectClasses.map((item: any) => (
+                  <Tab title={item.className} name={item.classGroupId}></Tab>
+                ))}
+              </Tabs>
+            )}
+          </div>
         </OSticky>
 
         {state.selectCourse.map((item: any) => (

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

@@ -40,27 +40,27 @@ export default defineComponent({
     const router = useRouter()
 
     // 查询没有设置指导老师的班级
-    const getClasses = async (show = true) => {
-      try {
-        const { data } = await request.post('/api-school/classGroup/page', {
-          data: {
-            page: 1,
-            rows: 200,
-            schoolId: state.user.data.school.id,
-            hasTeacher: false,
-            orchestraType: 'DELIVERY'
-          }
-        })
-        // 班级数据
-        forms.classList = data.rows || []
-        // 判断没有设置伴学指导的班级
-        if (forms.classList.length > 0 && show) {
-          forms.status = true
-        }
-      } catch {
-        //
-      }
-    }
+    // const getClasses = async (show = true) => {
+    //   try {
+    //     const { data } = await request.post('/api-school/classGroup/page', {
+    //       data: {
+    //         page: 1,
+    //         rows: 200,
+    //         schoolId: state.user.data.school.id,
+    //         hasTeacher: false,
+    //         orchestraType: 'DELIVERY'
+    //       }
+    //     })
+    //     // 班级数据
+    //     forms.classList = data.rows || []
+    //     // 判断没有设置伴学指导的班级
+    //     if (forms.classList.length > 0 && show) {
+    //       forms.status = true
+    //     }
+    //   } catch {
+    //     //
+    //   }
+    // }
 
     const onSubmit = () => {
       // 判断是否有班级没有设置伴学指导
@@ -108,7 +108,7 @@ export default defineComponent({
     }
 
     onMounted(() => {
-      getClasses()
+      // getClasses()
     })
     return () => (
       <div class={styles.practice}>
@@ -255,7 +255,7 @@ export default defineComponent({
           />
         </Popup>
 
-        <Dialog
+        {/* <Dialog
           v-model:show={forms.status}
           message={`您有${forms.classList.length}个班级尚未指定伴学指导,请完成指定后再进行训练规划。`}
           messageAlign="left"
@@ -289,7 +289,7 @@ export default defineComponent({
               getClasses(false)
             }}
           />
-        </OPopup>
+        </OPopup> */}
       </div>
     )
   }

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

@@ -148,7 +148,7 @@ export default defineComponent({
         ))}
 
         {base.contentList && base.contentList.length <= 0 && (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无训练内容" />
+          <OEmpty btnStatus={false} tips="暂无训练内容" />
         )}
 
         <OSticky position="bottom">

+ 2 - 0
src/school/train-planning/modal/class-list/index.module.less

@@ -37,9 +37,11 @@
   }
 
   .teacherName {
+    display: inline-block;
     font-size: 16px;
     font-weight: 500;
     color: #333333;
     line-height: 22px;
+    max-width: 80px;
   }
 }

+ 4 - 4
src/school/train-planning/modal/class-list/index.tsx

@@ -97,14 +97,14 @@ export default defineComponent({
                   <div class={[styles.name, 'van-ellipsis']}>{item.orchestraName}</div>
                 </div>
               ),
-              value: () => <span class={styles.teacherName}>{item.teacherName}</span>
+              value: () => (
+                <span class={[styles.teacherName, 'van-ellipsis']}>{item.teacherName}</span>
+              )
             }}
           </Cell>
         ))}
         {/* 判断是否有班级没有设置伴学指导 */}
-        {props.classList.length <= 0 && (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无班级" />
-        )}
+        {props.classList.length <= 0 && <OEmpty btnStatus={false} tips="暂无班级" />}
 
         <Sticky position="bottom">
           <div class={'btnGroup'}>

+ 4 - 1
src/school/train-planning/modal/practice-class/index.module.less

@@ -41,9 +41,12 @@
     display: flex;
     align-items: center;
 
+    .maxWidth {
+      max-width: 120px;
+    }
     .name {
       padding-top: 0;
-      max-width: 120px;
+
       font-size: 16px;
       font-weight: 600;
       color: #333333;

+ 20 - 3
src/school/train-planning/modal/practice-class/index.tsx

@@ -180,14 +180,30 @@ export default defineComponent({
           >
             <CheckboxGroup class={[styles.gridContainer, styles.gridClass]} v-model={forms.check}>
               {forms.list.map((item: any) => (
-                <CellGroup class={styles.classCellGroup} onClick={() => onSelect(item.id)}>
+                <CellGroup
+                  class={styles.classCellGroup}
+                  onClick={() => {
+                    if (item.teacherId) {
+                      onSelect(item.id)
+                    }
+                  }}
+                  border={false}
+                >
                   <Cell center titleStyle={{ flex: '0 auto' }} valueClass={styles.classCheckbox}>
                     {{
                       icon: () => <Image src={iconTeacher} class={styles.img} />,
                       title: () => (
                         <div class={styles.content}>
                           <div class={styles.teacherName}>
-                            <div class={[styles.name, 'van-ellipsis']}>{item.teacherName}</div>
+                            {item.teacherName ? (
+                              <div class={[styles.name, styles.maxWidth, 'van-ellipsis']}>
+                                {item.teacherName}
+                              </div>
+                            ) : (
+                              <div class={[styles.name, 'van-ellipsis']} style={{ color: 'red' }}>
+                                暂未设置伴学指导
+                              </div>
+                            )}
                             <Tag type="primary">{item.name}</Tag>
                           </div>
                           <div class={[styles.orchestraName, 'van-ellipsis']}>
@@ -199,6 +215,7 @@ export default defineComponent({
                         <Checkbox
                           name={item.id}
                           ref={(el: any) => (forms.checkboxRefs[item.id] = el)}
+                          disabled={item.teacherId ? false : true}
                           onClick={(e: any) => {
                             e.stopPropagation()
                           }}
@@ -227,7 +244,7 @@ export default defineComponent({
             </CheckboxGroup>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无班级" />
+          <OEmpty btnStatus={false} tips="暂无班级" />
         )}
 
         <OSticky position="bottom">

+ 1 - 1
src/student/coupons/list.tsx

@@ -69,7 +69,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          // <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无优惠券" />
+          // <ColResult btnStatus={false}   tips="暂无优惠券" />
           <span>11</span>
         )}
       </>

+ 112 - 0
src/student/download/transfer.tsx

@@ -0,0 +1,112 @@
+import { browser } from '@/helpers/utils'
+import { Button, showToast } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import wxBg from './images/wx_bg.png'
+import qs from 'query-string'
+
+export default defineComponent({
+  name: 'download-transfer',
+  data() {
+    return {
+      wxStatus: false,
+      type: 'student',
+      buttonText: '下载管乐团学生端'
+    }
+  },
+  mounted() {
+    const { pn, url, action, pageTag, ...rest } = this.$route.query
+    const { origin, pathname } = location
+    let tempPathname = pathname
+    let beforeIos = 'BandMusicTeam://linkUrl='
+    let beforeAndroid = 'colexiustudent://html:8888/SplashActivity?url='
+
+    if (pn === 's') {
+      tempPathname = '/orchestra-student/'
+      beforeIos = 'BandMusicTeam://linkUrl='
+      beforeAndroid = 'orchestrastudent://html:8888/SplashActivity?url='
+    } else if (pn === 't') {
+      tempPathname = '/orchestra-teacher/'
+      beforeIos = 'BandMusicTeamTeacher://linkUrl='
+      beforeAndroid = 'orchestrateacher://html:8888/SplashActivity?url='
+    } else if (pn === 'm') {
+      tempPathname = '/orchestra-school/'
+      beforeIos = 'BandMusicTeamManager://linkUrl='
+      beforeAndroid = 'orchestramanager://html:8888/SplashActivity?url='
+    }
+
+    let str = origin + tempPathname + '#/'
+    // 判断是否有跳转连接, 如果连接和动作没有时, 则不跳转
+    if (!url && !action) {
+      return
+    }
+    str +=
+      url +
+      qs.stringify({
+        ...rest
+      })
+
+    const query = {
+      url: str,
+      action: action || 'h5', // app, h5
+      pageTag: pageTag || 1 // 页面标识
+      // params: {}
+    } as any
+    const iosStr = encodeURIComponent(JSON.stringify(query))
+    console.log(query, 'iosStr')
+    if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
+      /*  
+        唤起前缀 IOS
+        BandMusicTeam:// 管乐团
+        BandMusicTeamTeacher://
+        BandMusicTeamManager:// */
+      /* 
+        唤起前缀 ANDROID
+        orchestrastudent://html:8888/SplashActivity?url=
+        orchestrateacher://html:8888/SplashActivity?url=
+        orchestramanager://html:8888/SplashActivity?url=
+       */
+      window.location.href = beforeIos + iosStr
+    } else if (/(Android)/i.test(navigator.userAgent)) {
+      window.location.href = beforeAndroid + iosStr
+    } else {
+      showToast('请用手机或移动设备打开')
+    }
+  },
+  methods: {
+    onDownload() {
+      if (browser().weixin) {
+        this.wxStatus = true
+        return
+      }
+
+      if (browser().ios || /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
+        window.location.href = 'https://itunes.apple.com/cn/app/id1626971695?mt=8'
+      } else if (/(Android)/i.test(navigator.userAgent)) {
+        window.location.href = 'https://appstore.ks3-cn-beijing.ksyuncs.com/clx-student-domain.apk'
+      } else {
+        showToast('请用手机或移动设备打开')
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={[styles.student]}>
+        <Button round size="large" color="#FF8057" class={styles.btn} onClick={this.onDownload}>
+          {this.buttonText}
+        </Button>
+
+        {this.wxStatus && (
+          <div
+            class={styles.wxpopup}
+            onClick={() => {
+              this.wxStatus = false
+            }}
+          >
+            <img src={wxBg} alt="" />
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 1 - 0
src/student/music-group/layout/auth.tsx

@@ -93,6 +93,7 @@ export default defineComponent({
               tips="加载失败,请稍后重试"
               buttonText="重新加载"
               plain={true}
+              btnStatus={true}
               onClick={this.setAuth}
             />
           </div>

+ 30 - 17
src/student/music-group/layout/index.module.less

@@ -74,29 +74,42 @@
     }
   }
 
+  .wxPopupDialog {
+    position: relative;
+    overflow: inherit;
+    margin-top: -100px;
+    &::before {
+      position: absolute;
+      content: ' ';
+      top: -73px;
+      left: 50%;
+      margin-left: -86px;
+      display: inline-block;
+      background: url('../pre-apply/images/wx-no-top.png') no-repeat top center;
+      background-size: contain;
+      width: 172px;
+      height: 154px;
+    }
+  }
   .popupContainer {
-    .dialogTitle {
-      i {
-        display: inline-block;
-        width: 4px;
-        height: 14px;
-        background: #ff8057;
-        border-radius: 2px;
-        margin-right: 6px;
-      }
-
-      text-align: left;
+    background: url('../pre-apply/images/wx-no-bg.png') no-repeat top center !important;
+    background-size: cover;
+    border-radius: 20px;
+    overflow: hidden;
+    .title {
+      padding-top: 57px;
+      text-align: center;
       font-size: 18px;
       font-weight: 500;
-      color: #333333;
-      line-height: 25px;
-      padding: 20px 0 20px 25px;
+      color: #3b2300;
     }
-
     .popupTips {
+      padding-top: 12px;
+      padding-bottom: 47px;
       text-align: center;
-      padding: 15px 0 45px;
-      font-size: 16px;
+      font-size: 15px;
+      color: #777777;
+      line-height: 21px;
     }
   }
 }

+ 36 - 29
src/student/music-group/layout/login.tsx

@@ -7,6 +7,7 @@ import { removeAuth, setAuth } from './utils'
 import styles from './index.module.less'
 import request from '@/helpers/request'
 import { browser, getUrlCode } from '@/helpers/utils'
+import qs from 'query-string'
 
 type loginType = 'PWD' | 'SMS'
 export default defineComponent({
@@ -22,6 +23,7 @@ export default defineComponent({
       // countDownRef: null as any, // 倒计时实例
       imgCodeStatus: false,
       showPopup: false,
+      wxAppId: '', //
       code: '' // 授权code码
     }
   },
@@ -37,35 +39,27 @@ export default defineComponent({
     this.directNext()
 
     // 判断是否是微信,只能微信中打开
-    if (browser().weixin) {
-      // 微信公众号支付
-      //授权
-      const code = getUrlCode()
-      console.log('login mounted code: ' + code)
-      if (!code) {
-        this.getAppIdAndCode()
-      } else {
-        this.code = code
-      }
-    } else {
+    if (!browser().weixin) {
       this.showPopup = true
     }
   },
   methods: {
-    async getAppIdAndCode() {
+    async getAppIdAndCode(url?: string) {
       try {
         const { data } = await request.get('/api-student/open/paramConfig/wechatAppId')
         // 判断是否有微信appId
         if (data) {
-          this.goAuth(data)
+          this.goAuth(data, url)
         }
       } catch {
         //
       }
     },
-    goAuth(wxAppId: string) {
+    goAuth(wxAppId: string, urlString?: string) {
       // 用户授权
-      const urlNow = encodeURIComponent(window.location.href)
+      console.log(urlString || window.location.href, 'urlString || window.location.href')
+      const urlNow = encodeURIComponent(urlString || window.location.href)
+      console.log(urlNow, 'urlNow')
       const scope = 'snsapi_base' //snsapi_userinfo   //静默授权 用户无感知
       const appid = wxAppId || 'wx8654c671631cfade'
       const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${urlNow}&response_type=code&scope=${scope}&state=STATE&connect_redirect=1#wechat_redirect`
@@ -81,13 +75,29 @@ export default defineComponent({
           },
           'jump pre registration'
         )
-        this.$router.replace({
-          path: returnUrl as any,
-          query: {
-            ...rest,
-            code: this.code
-          }
-        })
+
+        const newUrl =
+          window.location.origin +
+          window.location.pathname +
+          '#' +
+          returnUrl +
+          '?' +
+          qs.stringify({
+            ...rest
+          })
+        // 直接跳转到授权页面
+        this.getAppIdAndCode(newUrl)
+        // this.locationReplace(newUrl)
+      }
+    },
+    locationReplace(url: any) {
+      // 只允许同域名
+      console.log(history.replaceState, 'window.history.replaceState', url)
+      if (history.replaceState) {
+        history.replaceState(null, document.title, url)
+        history.go(0)
+      } else {
+        location.replace(url)
       }
     },
     async onLogin() {
@@ -100,7 +110,7 @@ export default defineComponent({
           autoRegister: true,
           password: this.smsCode,
           loginType: 'SMS',
-          grant_type: 'SMS'
+          grant_type: 'password'
         }
 
         const { data } = await request.post('/api-oauth/userlogin', {
@@ -221,15 +231,12 @@ export default defineComponent({
         <Popup
           v-model:show={this.showPopup}
           round
-          style={{ width: '92%' }}
+          style={{ width: '88%' }}
           closeOnClickOverlay={false}
+          class={styles.wxPopupDialog}
         >
           <div class={styles.popupContainer}>
-            <div class={styles.dialogTitle}>
-              <i></i>
-              提示
-            </div>
-
+            <p class={styles.title}>温馨提示</p>
             <p class={styles.popupTips}>请使用微信打开</p>
           </div>
         </Popup>

+ 19 - 15
src/student/music-group/pre-apply/component/apply.tsx

@@ -33,6 +33,10 @@ export default defineComponent({
     registerInfo: {
       type: Object,
       defualt: {}
+    },
+    code: {
+      type: String,
+      default: ''
     }
   },
   emits: ['next'],
@@ -40,7 +44,7 @@ export default defineComponent({
     const route = useRoute()
     const router = useRouter()
     const state = reactive({
-      code: '' as any, // 微信授权code码
+      // code: '' as any, // 微信授权code码
       detail: {} as any, // 学生详情
       currentGrade: [
         { text: '一年级', value: 1 },
@@ -157,7 +161,7 @@ export default defineComponent({
         await request.post('/api-student/orchestraRegister/save', {
           data: {
             ...params,
-            code: state.code
+            code: props.code
           }
         })
         setTimeout(() => {
@@ -170,8 +174,8 @@ export default defineComponent({
     }
 
     onMounted(async () => {
-      state.code = route.query.code || ''
-      console.log('pre register code: ' + state.code)
+      // state.code = route.query.code || ''
+      // console.log('pre register code: ' + state.code)
       await getSubjects()
       // 判断学年制
       if (props.schoolSystem === 'sixYearSystem') {
@@ -180,17 +184,17 @@ export default defineComponent({
       await studentRegister()
 
       // 判断是否有授权码
-      if (!state.code) {
-        setLogout()
-        const query = {
-          returnUrl: route.path,
-          ...route.query
-        } as any
-        router.replace({
-          path: '/loginMusic',
-          query: query
-        })
-      }
+      // if (!props.code) {
+      //   setLogout()
+      //   const query = {
+      //     returnUrl: route.path,
+      //     ...route.query
+      //   } as any
+      //   router.replace({
+      //     path: '/loginMusic',
+      //     query: query
+      //   })
+      // }
     })
     return () => (
       <>

+ 1 - 1
src/student/music-group/pre-apply/component/order.tsx

@@ -241,7 +241,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无订单" />
+          <OEmpty btnStatus={false} tips="暂无订单" />
         )}
 
         <Popup v-model:show={form.refundStatus} round style={{ width: '90%' }}>

BIN
src/student/music-group/pre-apply/images/banner.png


BIN
src/student/music-group/pre-apply/images/wx-no-bg.png


BIN
src/student/music-group/pre-apply/images/wx-no-top.png


+ 16 - 4
src/student/music-group/pre-apply/index.module.less

@@ -49,16 +49,28 @@
     display: flex;
     align-items: center;
     justify-content: center;
-    span {
+    flex-direction: column;
+    .orchestraName {
       display: block;
-      padding: 0 35px;
-      font-size: 30px;
-      font-weight: bold;
+      padding: 0 25px;
+      font-size: 24px;
+      font-weight: 600;
       color: #ffffff;
       line-height: 38px;
       letter-spacing: 1px;
+      text-shadow: 2px 2px 0px #d83d00;
+      text-align: justify;
       text-align: center;
     }
+    .tips {
+      margin-top: 24px;
+      padding: 5px 11px;
+      background-color: #fff;
+      border-radius: 16px 0 16px 0;
+      font-size: 15px;
+      font-weight: 600;
+      color: #e45729;
+    }
   }
 }
 

+ 98 - 45
src/student/music-group/pre-apply/index.tsx

@@ -22,9 +22,9 @@ export default defineComponent({
       heightV: 235,
       registerInfo: {} as any,
       purchase: false, // 购买状态
-      register: true // 是否注册
+      register: true, // 是否注册
       // showPopup: false,
-      // code: '' as any
+      code: '' as any
     })
 
     const onNext = async (name: string) => {
@@ -43,6 +43,20 @@ export default defineComponent({
           '/api-student/orchestraRegister/registerStatus/' + route.query.id
         )
         state.registerInfo = data || {}
+        const name = data.orchestraName
+        // const name = '华中科技大学大学同济医学院附'
+        // console.log(name.length)
+        if (name.length > 12 && name.length / 12 > 1 && name.length / 12 <= 2) {
+          const len = name.substring(12, 24)
+          if (len.length < 5) {
+            const splitLen = Math.ceil(name.length / 2)
+            const first = name.substring(0, splitLen)
+            const last = name.substring(splitLen, name.length)
+            state.registerInfo.orchestraName = first + '<br />' + last
+          } else {
+            state.registerInfo.orchestraName = name
+          }
+        }
 
         // 判断是否报名注册过
         state.register = data.register
@@ -87,6 +101,7 @@ export default defineComponent({
           return
         }
 
+        // 判断乐团
         if (data.registerOrchestra >= 1) {
           showDialog({
             title: '提示',
@@ -104,6 +119,20 @@ export default defineComponent({
           })
           return
         }
+
+        // 判断是否有openId 并且 purchase
+        // if (!data.openId && !data.purchase) {
+        //   if (browser().weixin) {
+        //     // 微信公众号支付
+        //     //授权
+        //     const code = getUrlCode()
+        //     if (!code) {
+        //       goAuth(data.wxAppId)
+        //     } else {
+        //       state.code = code
+        //     }
+        //   }
+        // }
       } catch {
         //
       }
@@ -123,55 +152,78 @@ export default defineComponent({
       }
     }
 
-    // const getAppIdAndCode = async () => {
-    //   try {
-    //     const { data } = await request.get('/api-student/open/paramConfig/wechatAppId')
-    //     // 判断是否有微信appId
-    //     if (data) {
-    //       goAuth(data)
-    //     }
-    //   } catch {
-    //     //
+    const goAuth = (wxAppId: string) => {
+      // 用户授权
+      const urlNow = encodeURIComponent(window.location.href)
+      const scope = 'snsapi_base' //snsapi_userinfo   //静默授权 用户无感知
+      const appid = wxAppId || 'wx8654c671631cfade'
+      const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${urlNow}&response_type=code&scope=${scope}&state=STATE&connect_redirect=1#wechat_redirect`
+      window.location.replace(url)
+    }
+
+    const getAppIdAndCode = async () => {
+      try {
+        const { data } = await request.get('/api-student/open/paramConfig/wechatAppId')
+        // 判断是否有微信appId
+        if (data) {
+          goAuth(data)
+        }
+      } catch {
+        //
+      }
+    }
+
+    // 先请求接口 判断是否有code
+    if (browser().weixin) {
+      // 微信公众号支付
+      //授权
+      const code = getUrlCode()
+      if (!code) {
+        getAppIdAndCode()
+      } else {
+        state.code = code
+        getRegisterStatus()
+      }
+    } else {
+      setLogout()
+      const query = {
+        returnUrl: route.path,
+        ...route.query
+      } as any
+      router.replace({
+        path: '/loginMusic',
+        query: query
+      })
+    }
+
+    // onMounted(() => {
+    // state.code = route.query.code || ''
+    // const { height } = useRect(bannerRef.value)
+    // state.heightV = height
+    // 判断是否是微信,只能微信中打开
+    // if (browser().weixin) {
+    //   // 微信公众号支付
+    //   //授权
+    //   const code = getUrlCode()
+    //   if (!code || !state.code) {
+    //     getAppIdAndCode()
+    //   } else {
+    //     state.code = code
     //   }
     // }
-    // const goAuth = (wxAppId: string) => {
-    //   // 用户授权
-    //   const urlNow = encodeURIComponent(window.location.href)
-    //   const scope = 'snsapi_base' //snsapi_userinfo   //静默授权 用户无感知
-    //   const appid = wxAppId || 'wx8654c671631cfade'
-    //   const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${urlNow}&response_type=code&scope=${scope}&state=STATE&connect_redirect=1#wechat_redirect`
-    //   window.location.replace(url)
-    // }
-
-    // 先请求接口
-    getRegisterStatus()
-
-    onMounted(() => {
-      // state.code = route.query.code || ''
-      // const { height } = useRect(bannerRef.value)
-      // state.heightV = height
-      // 判断是否是微信,只能微信中打开
-      // if (browser().weixin) {
-      //   // 微信公众号支付
-      //   //授权
-      //   const code = getUrlCode()
-      //   if (!code || !state.code) {
-      //     getAppIdAndCode()
-      //   } else {
-      //     state.code = code
-      //   }
-      // } else {
-      //   state.showPopup = true
-      // }
-    })
+    // })
     return () => (
       <div class={styles.preApply}>
         <div class={styles.banner} ref={bannerRef}>
-          <span>
-            {state.registerInfo.orchestraName} <br />
-            乐团报名
-          </span>
+          {state.registerInfo.orchestraName && (
+            <>
+              <p class={styles.orchestraName} v-html={state.registerInfo.orchestraName}></p>
+
+              <div class={styles.tips}>快来参加乐团报名吧!</div>
+            </>
+          )}
         </div>
+
         <Sticky position="top">
           <Tabs
             lineWidth={20}
@@ -188,6 +240,7 @@ export default defineComponent({
         {state.tabValue === 'apply' && (
           <Apply
             onNext={onNext}
+            code={state.code}
             registerInfo={state.registerInfo}
             schoolSystem={state.registerInfo.schoolSystem}
           />

+ 29 - 5
src/student/music-group/pre-apply/order-detail.tsx

@@ -13,6 +13,7 @@ import { browser, moneyFormat } from '@/helpers/utils'
 import OProtocol from '@/components/o-protocol'
 import OPopup from '@/components/o-popup'
 import UserAuth from './component/user-auth'
+import qs from 'query-string'
 
 export default defineComponent({
   name: 'order-detail',
@@ -106,17 +107,29 @@ export default defineComponent({
       const config: any = state.config
       state.pay_channel = val.pay_channel
       if (val.payCode === 'payResult') {
-        router.push({
-          path: '/payResult',
-          query: {
+        // router.push({
+        //   path: '/payResult',
+        //   query: {
+        //     pay_channel: val.pay_channel,
+        //     wxAppId: config.wxAppId,
+        //     body: config.body,
+        //     price: config.price,
+        //     orderNo: config.merOrderNo,
+        //     userId: config.userId
+        //   }
+        // })
+
+        window.location.href =
+          window.location.origin +
+          '/orchestra-student/#/payResult?' +
+          qs.stringify({
             pay_channel: val.pay_channel,
             wxAppId: config.wxAppId,
             body: config.body,
             price: config.price,
             orderNo: config.merOrderNo,
             userId: config.userId
-          }
-        })
+          })
       } else {
         state.qrCodeUrl =
           window.location.origin +
@@ -176,6 +189,17 @@ export default defineComponent({
       }, 5000)
     }
 
+    // const locationReplace = (url: any) => {
+    //   // 只允许同域名
+    //   console.log(history.replaceState, 'window.history.replaceState', url)
+    //   if (history.replaceState) {
+    //     history.replaceState(null, document.title, url)
+    //     history.go(0)
+    //   } else {
+    //     location.replace(url)
+    //   }
+    // }
+
     const beforeSubmit = () => {
       const pt = state.pay_channel
       let payCode = 'qrCode'

+ 2 - 2
src/student/music-group/shop-address/address-operation.tsx

@@ -40,8 +40,8 @@ export default defineComponent({
           showToast('请选择收货人')
           return
         }
-        if (!state.phoneNumber) {
-          showToast('请输入手机号')
+        if (!state.phoneNumber && !/^1(3|4|5|6|7|8|9)\d{9}$/.test(state.phoneNumber as any)) {
+          showToast('手机号输入有误')
           return
         }
         if (!state.pcrStr) {

+ 1 - 1
src/student/music-group/shop-address/index.tsx

@@ -160,7 +160,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无收货地址" />
+          <OEmpty btnStatus={false} tips="暂无收货地址" />
         )}
         <OSticky position="bottom">
           <div class={'btnGroup'}>

+ 2 - 2
src/student/my-orchestra/index.tsx

@@ -252,11 +252,11 @@ export default defineComponent({
                 </div>
               </List>
             ) : (
-              <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无训练相片" />
+              <OEmpty btnStatus={false} tips="暂无训练相片" />
             )}
           </>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无乐团" />
+          <OEmpty btnStatus={false} tips="暂无乐团" />
         )}
 
         <Popup v-model:show={state.orchestraStatus} position="bottom" round>

+ 1 - 1
src/student/my-orchestra/photo-detail.tsx

@@ -97,7 +97,7 @@ export default defineComponent({
             </div>
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无相片" />
+          <OEmpty btnStatus={false} tips="暂无相片" />
         )}
       </div>
     )

+ 1 - 1
src/student/trade-record/component/paid-list.tsx

@@ -261,7 +261,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无订单" />
+          <OEmpty btnStatus={false} tips="暂无订单" />
         )}
 
         <Popup v-model:show={form.timeShow} position="bottom" round>

+ 1 - 1
src/student/trade-record/component/refund-list.tsx

@@ -156,7 +156,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无订单" />
+          <OEmpty btnStatus={false} tips="暂无订单" />
         )}
 
         <Popup v-model:show={form.timeShow} position="bottom" round>

+ 1 - 1
src/student/trade-record/component/wait-pay.tsx

@@ -240,7 +240,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无订单" />
+          <OEmpty btnStatus={false} tips="暂无订单" />
         )}
 
         <Popup v-model:show={form.timeShow} position="bottom" round>

+ 1 - 1
src/styles/index.less

@@ -124,7 +124,7 @@ body {
 
 .btnGroup {
   padding: 0 25px;
-  padding-bottom: 24px;
+  padding-bottom: calc(20px + env(safe-area-inset-bottom));
   .van-button {
     font-size: 16px !important;
     font-weight: 500;

+ 34 - 35
src/teacher/attendance/index.tsx

@@ -20,6 +20,7 @@ import request from '@/helpers/request'
 import { state as globalState } from '@/state'
 import { courseEmnu } from '@/constant'
 import TeacherAttItem from './modals/teacherAtt-item'
+import OSticky from '@/components/o-sticky'
 export default defineComponent({
   name: 'attend-student',
   props: {
@@ -159,47 +160,45 @@ export default defineComponent({
     }
     return () => (
       <>
-        <Sticky offsetTop={toTop.value}>
-          <div>
-            <OHeader></OHeader>
-            <div class={styles.chioseWrap}>
-              <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
-                <div
-                  class={styles.searchBand}
-                  onClick={() => {
-                    state.showPopoverTime = true
-                  }}
-                >
-                  {forms.timeName}
-                  <Icon name={state.showPopoverTime ? 'arrow-up' : 'arrow-down'} />
-                </div>
+        <OSticky position="top">
+          <OHeader />
+          <div class={styles.chioseWrap}>
+            <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
+              <div
+                class={styles.searchBand}
+                onClick={() => {
+                  state.showPopoverTime = true
+                }}
+              >
+                {forms.timeName}
+                <Icon name={state.showPopoverTime ? 'arrow-up' : 'arrow-down'} />
               </div>
+            </div>
 
-              <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
-                <div
-                  class={styles.searchBand}
-                  onClick={() => {
-                    state.showPopoverOrchestra = true
-                  }}
-                >
-                  {forms.orchestraName}
-                  <Icon name={state.showPopoverOrchestra ? 'arrow-up' : 'arrow-down'} />
-                </div>
+            <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
+              <div
+                class={styles.searchBand}
+                onClick={() => {
+                  state.showPopoverOrchestra = true
+                }}
+              >
+                {forms.orchestraName}
+                <Icon name={state.showPopoverOrchestra ? 'arrow-up' : 'arrow-down'} />
               </div>
-              <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
-                <div
-                  class={styles.searchBand}
-                  onClick={() => {
-                    state.showPopoverSubject = true
-                  }}
-                >
-                  {forms.courseTypeName}
-                  <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
-                </div>
+            </div>
+            <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
+              <div
+                class={styles.searchBand}
+                onClick={() => {
+                  state.showPopoverSubject = true
+                }}
+              >
+                {forms.courseTypeName}
+                <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
               </div>
             </div>
           </div>
-        </Sticky>
+        </OSticky>
 
         {showContact.value ? (
           <PullRefresh v-model={refreshing.value} onRefresh={onRefresh} style="min-height: 100vh;">

+ 0 - 1
src/teacher/screen-projection/index.tsx

@@ -61,7 +61,6 @@ export default defineComponent({
     return () => (
       <div>
         <OHeader
-          isBack
           rightText="投屏帮助"
           onClickRight={() => {
             console.log('打开投屏')

+ 4 - 4
src/views/accompany/music-list.tsx

@@ -134,8 +134,8 @@ export default defineComponent({
     })
     //进入云教练
     const openView = (item: any) => {
-      const Authorization = sessionStorage.getItem('Authorization') || ''
-      let src = `${location.origin}/orchestra-music-score/?id=${item.id}&Authorization=${Authorization}`
+      let src = `${location.origin}/orchestra-music-score/?id=${item.id}`
+      console.log("🚀 ~ 去云教练的src", src)
       postMessage({
         api: 'openAccompanyWebView',
         content: {
@@ -206,10 +206,10 @@ export default defineComponent({
         {headerData.height && <div style={{height: headerData.height + 'px'}}></div>}
         {/* <Cell
           center
-          title="无别,胜强测试"
+          title="胜强测试"
           isLink
           onClick={() => {
-            let src = `http://192.168.3.114:3000/orchestra-music-score/?id=1603573996544364546&Authorization=${sessionStorage.getItem('Authorization')}`
+            let src = `http://192.168.3.114:3000/orchestra-music-score/?id=1603573996544364546`
             console.log("🚀 ~ 去云教练的src", src)
             if (browser().isApp) {
               postMessage({

+ 1 - 1
src/views/adapay/use-coupons/choice-coupon.tsx

@@ -162,7 +162,7 @@ export default defineComponent({
                   ))}
                 </>
               ) : (
-                <OEmpty btnStatus={false} tips="暂无优惠券" classImgSize="SMALL" />
+                <OEmpty btnStatus={false} tips="暂无优惠券" />
               )}
             </>
           ) : (

BIN
src/views/courseList/image/icon-course.png


+ 1 - 1
src/views/courseList/index.module.less

@@ -42,7 +42,7 @@
       align-items: center;
       justify-content: center;
       font-size: 12px;
-      width: 50%;
+      width: 70%;
       height: 20px;
       border-radius: 20px;
       background: linear-gradient(180deg, #ff9c7c 0%, #ff5757 100%);

+ 115 - 28
src/views/courseList/index.tsx

@@ -1,16 +1,25 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Button, Empty, Grid, GridItem, Icon, showToast } from 'vant'
-import { defineComponent, onMounted, reactive } from 'vue'
+import { Button, Empty, Grid, GridItem, Icon, showConfirmDialog, showToast } from 'vant'
+import { defineComponent, onMounted, reactive, onUnmounted } from 'vue'
 import styles from './index.module.less'
-import iconLook from './image/look.svg'
 import { useRoute, useRouter } from 'vue-router'
-import { postMessage } from '@/helpers/native-message'
+import {
+  listenerMessage,
+  postMessage,
+  promisefiyPostMessage,
+  removeListenerMessage
+} from '@/helpers/native-message'
+import iconLook from './image/look.svg'
+import iconCourse from './image/icon-course.png'
+import { browser } from '@/helpers/utils'
 export default defineComponent({
   name: 'lessonCourseware',
   setup() {
     const route = useRoute()
     const router = useRouter()
+    const browserInfo = browser()
+    // const catchList = store
     const data = reactive({
       loading: true,
       list: [] as any
@@ -38,7 +47,8 @@ export default defineComponent({
             state.platformApi + '/courseSchedule/myCoursewareDetail/' + route.query.id
           )
           if (Array.isArray(res?.data)) {
-            data.list = res.data
+            // data.list = res.data
+            data.list = browserInfo.isApp ? await checkCoursewareCache(res.data) : res.data
           }
         } catch (error) {}
       }
@@ -46,14 +56,36 @@ export default defineComponent({
     }
     onMounted(() => {
       getList()
+      listenerMessage('downloadCoursewareToCache', getProgress)
+    })
+    onUnmounted(() => {
+      removeListenerMessage('downloadCoursewareToCache', getProgress)
     })
 
-    const handleClick = (item: any) => {
+    const handleClick = async (item: any) => {
       if (route.query.code === 'select') {
         console.log('选择课时')
         setCoursewareDetail(item)
         return
       }
+      if (!item.hasCache) {
+        if (browserInfo.isStudent || route.query.isdev) {
+          try {
+            await showConfirmDialog({
+              message: '当前课程没有缓存是否缓存'
+            })
+          } catch (error) {
+            gotoPlay(item)
+            return
+          }
+        }
+        downCatch(item)
+        return
+      }
+      gotoPlay(item)
+    }
+    // 去课件播放
+    const gotoPlay = (item: any) => {
       router.push({
         path: '/coursewarePlay',
         query: {
@@ -61,6 +93,58 @@ export default defineComponent({
         }
       })
     }
+    // 检查数据的缓存状态
+    const checkCoursewareCache = (list: []) => {
+      return new Promise((resolve) => {
+        postMessage(
+          {
+            api: 'checkCoursewareCache',
+            content: {
+              data: list
+            }
+          },
+          (res) => {
+            if (res?.content?.data) {
+              resolve(res.content.data)
+              return
+            }
+            return []
+          }
+        )
+      })
+    }
+    // 下载缓存
+    const downCatch = async (item: any) => {
+      if (browserInfo.isApp) {
+        const res = await postMessage({
+          api: 'downloadCoursewareToCache',
+          content: {
+            data: item
+          }
+        })
+        return res
+      }
+
+      return true
+    }
+    // 下载缓存进度
+    const getProgress = (res: any) => {
+      // console.log('🚀 ~ res', res)
+      if (res?.content?.lessonCoursewareDetailId) {
+        const { lessonCoursewareDetailId, downloadStatus, progress } = res.content
+        const course = data.list.find(
+          (n: any) => n.lessonCoursewareDetailId == lessonCoursewareDetailId
+        )
+        if (course) {
+          course.downloadStatus = downloadStatus
+          course.progress = progress
+          if (downloadStatus == 2) {
+            course.hasCache = 1
+            course.progress = 100
+          }
+        }
+      }
+    }
     // 绑定课时
     const setCoursewareDetail = async (item: any) => {
       try {
@@ -85,25 +169,33 @@ export default defineComponent({
           {data.list.map((item: any) => {
             return (
               <GridItem>
-                <div
-                  class={styles.gridItem}
-                  style={{
-                    background: item.coverImg
-                      ? ''
-                      : `hsla(${Math.floor(Math.random() * 360)},50%,50%,.8)`
-                  }}
-                  onClick={() => handleClick(item)}
-                >
-                  {/* <img src={item.coverImg} class={styles.cover} /> */}
+                <div class={styles.gridItem} onClick={() => handleClick(item)}>
+                  <img src={iconCourse} class={styles.cover} />
                   <div class={styles.title}>
                     <div>{item.coursewareDetailName}</div>
                     {route.query.code !== 'select' && <div>已使用 {item.useNum} 次</div>}
                   </div>
                   {route.query.code !== 'select' ? (
-                    <div class={styles.num}>
-                      查看
-                      <Icon name="play-circle-o" />
-                    </div>
+                    <>
+                      {item.hasCache ? (
+                        <div class={styles.num}>
+                          查看
+                          <Icon name="play-circle-o" />
+                        </div>
+                      ) : (
+                        <>
+                          {item.downloadStatus === 1 ? (
+                            <div class={styles.num}>下载中 {item.progress || 0}%</div>
+                          ) : item.downloadStatus === 2 ? (
+                            <div class={styles.num}>下载成功</div>
+                          ) : item.downloadStatus === 3 ? (
+                            <div class={styles.num}>重新下载</div>
+                          ) : (
+                            <div class={styles.num}>下载</div>
+                          )}
+                        </>
+                      )}
+                    </>
                   ) : (
                     <div class={styles.num}>选择</div>
                   )}
@@ -118,15 +210,10 @@ export default defineComponent({
             )
           })}
         </Grid>
+        <Button onClick={() => {
+          location.href = 'http://192.168.3.114:1000/teacher.html#/coursewarePlay?id=1613426640725217281'
+        }}>胜强测试老师端</Button>
         {!data.loading && !data.list.length && <Empty description="空空如也" />}
-        {/* <Button
-          onClick={() => {
-            location.href =
-              'http://192.168.3.114:1000/teacher.html#/coursewarePlay?id=1610595720511209474&courseId=1612270880549044226'
-          }}
-        >
-          测试
-        </Button> */}
       </div>
     )
   }

+ 13 - 2
src/views/coursewarePlay/component/musicScore.tsx

@@ -10,7 +10,9 @@ export default defineComponent({
       default: () => {}
     }
   },
-  setup(props, {}) {
+  emits: ['setIframe'],
+  setup(props, { emit }) {
+    const iframeRef = ref()
     const Authorization = sessionStorage.getItem('Authorization') || ''
     const origin = /(localhost|192)/.test(location.host)
       ? 'https://ponline.colexiu.com'//'http://192.168.3.114:3000'
@@ -18,13 +20,22 @@ export default defineComponent({
     const query = qs.stringify({
       id: props.music.content,
       modelType: 'practice',
+      headerHeight: 32,
       Authorization: Authorization
     })
     let src = `${origin}/orchestra-music-score/?` + query
     console.log('src', src)
     return () => (
       <div class={styles.musicScore}>
-        <iframe class={[styles.container, 'musicIframe']} frameborder="0" src={src}></iframe>
+        <iframe
+          ref={iframeRef}
+          onLoad={() => {
+            emit('setIframe', iframeRef.value)
+          }}
+          class={[styles.container, 'musicIframe']}
+          frameborder="0"
+          src={src}
+        ></iframe>
       </div>
     )
   }

+ 21 - 1
src/views/coursewarePlay/component/point.module.less

@@ -1,6 +1,8 @@
 .container {
+  display: flex;
+  flex-direction: column;
   min-width: 217px;
-  min-height: 100vh;
+  height: 100vh;
   color: #fff;
   font-size: 12px;
   box-sizing: border-box;
@@ -9,12 +11,17 @@
   display: flex;
   align-items: center;
   padding: 13px 10px 15px 15px;
+  flex-shrink: 0;
   img {
     width: 16px;
     height: 16px;
     margin-right: 7px;
   }
 }
+.content{
+    flex: 1;
+    overflow-y: auto;
+}
 .collapse {
   &:after {
     display: none;
@@ -51,10 +58,23 @@
   }
 }
 .item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
   padding: 4px 5px;
   border-radius: 6px;
+  :global {
+    .van-icon {
+      display: none;
+    }
+  }
 }
 .itemActive {
   background: rgba(0, 0, 0, 0.15);
   color: var(--van-primary);
+  :global {
+    .van-icon {
+      display: block;
+    }
+  }
 }

+ 33 - 11
src/views/coursewarePlay/component/points.tsx

@@ -1,8 +1,9 @@
-import { defineComponent, reactive } from 'vue'
+import { defineComponent, reactive, watch } from 'vue'
 import styles from './point.module.less'
 import iconMulv from '../image/icon-mulv.svg'
 import iconArrow from '../image/icon-arrow.svg'
-import { Collapse, CollapseItem } from 'vant'
+import iconZhibo from '../image/icon-zhibo.svg'
+import { Collapse, CollapseItem, Icon } from 'vant'
 export default defineComponent({
   name: 'points',
   props: {
@@ -22,15 +23,21 @@ export default defineComponent({
   emits: ['handleSelect'],
   setup(props, { emit }) {
     const pointData = reactive({
-      active: props.tabActive || '1610827376354500609' || ''
+      active: props.tabActive || ''
     })
+    watch(
+      () => props.tabActive,
+      () => {
+        pointData.active = props.tabActive
+      }
+    )
     return () => (
       <div class={styles.container}>
-        <div>
-          <div class={styles.pointHead}>
-            <img src={iconMulv} />
-            课程目录
-          </div>
+        <div class={styles.pointHead}>
+          <img src={iconMulv} />
+          课程目录
+        </div>
+        <div class={styles.content}>
           <Collapse
             class={styles.collapse}
             modelValue={pointData.active}
@@ -47,9 +54,24 @@ export default defineComponent({
                       <>
                         {Array.isArray(item?.materialList) &&
                           item.materialList.map((n: any) => {
-                            return <div class={[styles.item, props.itemActive == n.id ? styles.itemActive : '']} onClick={() => {
-                              emit('handleSelect', {itemActive: n.id, tabActive: item.id})
-                            }}>{n.name}</div>
+                            return (
+                              <div
+                                class={[
+                                  styles.item,
+                                  props.itemActive == n.id ? styles.itemActive : ''
+                                ]}
+                                onClick={() => {
+                                  emit('handleSelect', {
+                                    itemActive: n.id,
+                                    tabActive: item.id,
+                                    tabName: item.name
+                                  })
+                                }}
+                              >
+                                {n.name}
+                                <Icon name={iconZhibo} />
+                              </div>
+                            )
                           })}
                       </>
                     ),

+ 22 - 0
src/views/coursewarePlay/image/icon-down.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12.619886,7.97254617 L12.7071068,8.05025253 L16.2426407,11.5857864 C16.633165,11.9763107 16.633165,12.6094757 16.2426407,13 C15.880011,13.3626297 15.3081656,13.3885318 14.9156479,13.0777064 L14.8284271,13 L12.9994661,11.1713593 L13,19 C13,19.5522847 12.5522847,20 12,20 C11.4477153,20 11,19.5522847 11,19 L10.9994661,11.1703593 L9.17157288,13 C8.80894318,13.3626297 8.23709778,13.3885318 7.84458013,13.0777064 L7.75735931,13 C7.39472961,12.6373703 7.36882749,12.0655249 7.67965295,11.6730073 L7.75735931,11.5857864 L11.2928932,8.05025253 C11.6555229,7.68762283 12.2273683,7.66172071 12.619886,7.97254617 Z M18.363961,5.63603897 C21.8786797,9.15075759 21.8786797,14.8492424 18.363961,18.363961 C17.9734367,18.7544853 17.3402718,18.7544853 16.9497475,18.363961 C16.5592232,17.9734367 16.5592232,17.3402718 16.9497475,16.9497475 C19.6834175,14.2160774 19.6834175,9.78392257 16.9497475,7.05025253 C14.2160774,4.31658249 9.78392257,4.31658249 7.05025253,7.05025253 C4.31658249,9.78392257 4.31658249,14.2160774 7.05025253,16.9497475 C7.44077682,17.3402718 7.44077682,17.9734367 7.05025253,18.363961 C6.65972824,18.7544853 6.02656326,18.7544853 5.63603897,18.363961 C2.12132034,14.8492424 2.12132034,9.15075759 5.63603897,5.63603897 C9.15075759,2.12132034 14.8492424,2.12132034 18.363961,5.63603897 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-35.3%" width="166.7%" height="170.6%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放/全屏(伴学端)" transform="translate(-90.000000, -227.000000)" fill-rule="nonzero">
+            <g id="编组-8备份" transform="translate(82.000000, 68.000000)">
+                <g id="形状结合" transform="translate(20.000000, 171.000000) rotate(-180.000000) translate(-20.000000, -171.000000) translate(8.000000, 159.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 22 - 0
src/views/coursewarePlay/image/icon-up.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <path d="M12.619886,7.97254617 L12.7071068,8.05025253 L16.2426407,11.5857864 C16.633165,11.9763107 16.633165,12.6094757 16.2426407,13 C15.880011,13.3626297 15.3081656,13.3885318 14.9156479,13.0777064 L14.8284271,13 L12.9994661,11.1713593 L13,19 C13,19.5522847 12.5522847,20 12,20 C11.4477153,20 11,19.5522847 11,19 L10.9994661,11.1703593 L9.17157288,13 C8.80894318,13.3626297 8.23709778,13.3885318 7.84458013,13.0777064 L7.75735931,13 C7.39472961,12.6373703 7.36882749,12.0655249 7.67965295,11.6730073 L7.75735931,11.5857864 L11.2928932,8.05025253 C11.6555229,7.68762283 12.2273683,7.66172071 12.619886,7.97254617 Z M18.363961,5.63603897 C21.8786797,9.15075759 21.8786797,14.8492424 18.363961,18.363961 C17.9734367,18.7544853 17.3402718,18.7544853 16.9497475,18.363961 C16.5592232,17.9734367 16.5592232,17.3402718 16.9497475,16.9497475 C19.6834175,14.2160774 19.6834175,9.78392257 16.9497475,7.05025253 C14.2160774,4.31658249 9.78392257,4.31658249 7.05025253,7.05025253 C4.31658249,9.78392257 4.31658249,14.2160774 7.05025253,16.9497475 C7.44077682,17.3402718 7.44077682,17.9734367 7.05025253,18.363961 C6.65972824,18.7544853 6.02656326,18.7544853 5.63603897,18.363961 C2.12132034,14.8492424 2.12132034,9.15075759 5.63603897,5.63603897 C9.15075759,2.12132034 14.8492424,2.12132034 18.363961,5.63603897 Z" id="path-1"></path>
+        <filter x="-33.3%" y="-35.3%" width="166.7%" height="170.6%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放/全屏(伴学端)" transform="translate(-90.000000, -82.000000)" fill-rule="nonzero">
+            <g id="编组-8备份" transform="translate(82.000000, 68.000000)">
+                <g id="形状结合" transform="translate(8.000000, 14.000000)">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/views/coursewarePlay/image/icon-videobg.png


+ 1 - 0
src/views/coursewarePlay/image/icon-zhibo.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1673454396647" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1930" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M74.666667 853.333333c-17.066667 0-32-14.933333-32-32v-426.666666c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v426.666666c0 17.066667-14.933333 32-32 32zM366.933333 853.333333c-17.066667 0-32-14.933333-32-32v-618.666666c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v618.666666c0 17.066667-14.933333 32-32 32zM657.066667 853.333333c-17.066667 0-32-14.933333-32-32v-341.333333c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v341.333333c0 17.066667-12.8 32-32 32zM949.333333 853.333333c-17.066667 0-32-14.933333-32-32v-512c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v512c0 17.066667-14.933333 32-32 32z" fill="#FF8057" p-id="1931"></path></svg>

+ 0 - 1
src/views/coursewarePlay/index.module.less

@@ -113,7 +113,6 @@
   bottom: 0;
   z-index: 10;
   background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
-  backdrop-filter: blur(2px);
   .time {
     display: flex;
     justify-content: space-between;

+ 168 - 70
src/views/coursewarePlay/index.tsx

@@ -23,12 +23,11 @@ import {
 } from 'vue'
 import iconBack from './image/back.svg'
 import styles from './index.module.less'
-import Plyr from 'plyr'
 import 'plyr/dist/plyr.css'
 import request from '@/helpers/request'
 import { state } from '@/state'
 import { useRoute } from 'vue-router'
-import { listenerMessage, postMessage } from '@/helpers/native-message'
+import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message'
 import MusicScore from './component/musicScore'
 import iconMenu from './image/icon-menu.svg'
 import iconDian from './image/icon-dian.svg'
@@ -37,25 +36,32 @@ import iconLoop from './image/icon-loop.svg'
 import iconLoopActive from './image/icon-loop-active.svg'
 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 { getSecondRPM } from '@/helpers/utils'
+import { browser, getSecondRPM } from '@/helpers/utils'
+import { useRect } from '@vant/use'
 
 export default defineComponent({
   name: 'CoursewarePlay',
   setup() {
     const handleInit = (type = 0) => {
+      // 横屏
       postMessage({
         api: 'setRequestedOrientation',
         content: {
           orientation: type
         }
       })
+      // 头,包括返回箭头
       postMessage({
-        api: 'setBarStatus',
+        api: 'setTitleBarVisibility',
         content: {
           status: type
         }
       })
+      // 安卓的状态栏
       postMessage({
         api: 'setStatusBarVisibility',
         content: {
@@ -66,17 +72,16 @@ export default defineComponent({
     handleInit()
     onUnmounted(() => {
       handleInit(1)
+      window.removeEventListener('message', iframeHandle)
     })
 
     const route = useRoute()
+    const headeRef = ref()
     const data = reactive({
       detail: null,
-      active: '',
-      itemActive: '',
       knowledgePointList: [] as any,
       itemList: [] as any,
-      showHead: true,
-      players: [] as any
+      showHead: true
     })
     const activeData = reactive({
       nowTime: 0,
@@ -87,38 +92,89 @@ export default defineComponent({
       timer: null as any,
       item: null as any
     })
-    const getItemList = () => {
+    watch(
+      () => activeData.model,
+      () => {
+        const videoItem = data.itemList.find((n) => n.id === popupData.itemActive)
+        // 阴影切换的时候,具体去切换某个视频的控件
+        if (videoItem && videoItem.type === 'VIDEO') {
+          videoItem.playModel = activeData.model
+        }
+      }
+    )
+    // 获取缓存路径
+    const getCacheFilePath = async (material: any) => {
+      const res = await promisefiyPostMessage({
+        api: 'getCourseFilePath',
+        content: {
+          url: material.content,
+          localPath: '',
+          materialId: material.id,
+          updateTime: material.updateTime,
+          type: material.type // SONG VIDEO IMAGE
+        }
+      })
+      console.log('缓存路径返回', res)
+      return res
+    }
+    const getItemList = async () => {
       const list: any = []
+      const browserInfo = browser()
+      let _item = null
       for (let i = 0; i < data.knowledgePointList.length; i++) {
         const item = data.knowledgePointList[i]
+        const itemLength = item.materialList.length - 1
         for (let j = 0; j < item.materialList.length; j++) {
           const material = item.materialList[j]
-          if (popupData.itemActive === '') {
-            popupData.tabName = item.name
-            popupData.tabActive = material.knowledgePointId
-            popupData.itemActive = material.id
-            popupData.itemName = material.name
-            popupData.activeIndex = 0
+          //请求本地缓存
+          if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) {
+            const localData = await getCacheFilePath(material)
+            if (localData?.content?.localPath) {
+              material.url = material.content
+              material.content = localData.content.localPath
+              // console.log("🚀 ~ material", material)
+            }
           }
+
           let videoItem = {}
           if (material.type === 'VIDEO') {
             videoItem = {
               currentTime: 0,
-              duration: 0,
+              duration: 100,
               paused: true,
               loop: false,
               videoEle: null,
-              timer: null
+              timer: null,
+              playModel: true
             }
           }
           list.push({
             ...material,
-            ...videoItem
+            ...videoItem,
+            iframeRef: null,
+            tabName: item.name,
+            autoPlay: j === itemLength
           })
         }
       }
+
+      let item: any = null
+      if (route.query.kId) {
+        item = list.find((n: any) => n.id == route.query.kId)
+        const _firstIndex = list.findIndex((n: any) => n.id == route.query.kId)
+        popupData.firstIndex = _firstIndex > -1 ? _firstIndex : 0
+      } else {
+        item = list[0] || {}
+      }
+      if (item) {
+        popupData.tabName = item.tabName
+        popupData.tabActive = item.knowledgePointId
+        popupData.itemActive = item.id
+        popupData.itemName = item.name
+        popupData.activeIndex = popupData.firstIndex
+      }
       console.log('🚀 ~ list', list)
-      return list
+      data.itemList = list
     }
     const getDetail = async () => {
       try {
@@ -133,21 +189,29 @@ export default defineComponent({
             n.index = 0
             return n
           })
-          data.itemList = getItemList()
+          getItemList()
         }
       } catch (error) {}
     }
+    // ifram事件处理
+    const iframeHandle = (ev: MessageEvent) => {
+      // console.log(ev.data)
+      if (ev.data?.api === 'headerTogge') {
+        activeData.model = ev.data.show
+      }
+    }
     onMounted(() => {
       getDetail()
+      window.addEventListener('message', iframeHandle)
     })
     // 返回
     const goback = () => {
-      // history.go(-1)
-      postMessage({ api: 'back' })
+      postMessage({ api: 'goBack' })
     }
 
     const swipeRef = ref()
     const popupData = reactive({
+      firstIndex: 0,
       open: false,
       activeIndex: -1,
       tabActive: '',
@@ -156,8 +220,9 @@ export default defineComponent({
       itemName: ''
     })
     // 设置当前的激活状态
-    const setActiveData = () => {
+    const setActiveData = (val: any, oldVal: any) => {
       handleStopVideo()
+      handleStopMusicScore()
     }
     watch(() => popupData.activeIndex, setActiveData)
 
@@ -167,16 +232,11 @@ export default defineComponent({
         m.videoEle?.pause()
       })
     }
-    // 获取name
-    const setAllName = () => {
-      const item = data.itemList.find((n: any) => n.id == popupData.itemActive)
-      const tab = data.knowledgePointList.find((n: any) => n.id == popupData.tabActive)
-      if (item) {
-        popupData.itemName = item.name
-      }
-      if (tab) {
-        popupData.tabName = tab.name
-      }
+    // 停止曲谱的播放
+    const handleStopMusicScore = () => {
+      data.itemList.forEach((m: any) => {
+        m.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
+      })
     }
     // 切换素材
     const toggleMaterial = () => {
@@ -184,7 +244,6 @@ export default defineComponent({
       if (index > -1) {
         popupData.activeIndex = index
         swipeRef.value?.swipeTo(index)
-        setAllName()
       }
     }
     // 轮播切换
@@ -194,7 +253,8 @@ export default defineComponent({
       if (item) {
         popupData.tabActive = item.knowledgePointId
         popupData.itemActive = item.id
-        setAllName()
+        popupData.itemName = item.name
+        popupData.tabName = item.tabName
       }
     }
     // 上一个知识点, 下一个知识点
@@ -236,19 +296,10 @@ export default defineComponent({
         item.timer = setTimeout(() => {
           activeData.model = false
         }, 3000)
-        console.dir(videoEle)
+        // console.dir(videoEle)
       }
     }
 
-    // 视频播放
-    const handleVideoPlay = (e: Event) => {
-      const videoEle = e.target! as unknown as HTMLVideoElement
-      // console.log(videoEle.paused, videoEle.currentTime, videoEle.duration)
-      if (videoEle.paused) return
-      activeData.currentTime = videoEle.currentTime
-      activeData.duration = videoEle.duration
-    }
-
     // 调整播放进度
     const handleChangeSlider = (val: any, m: any) => {
       if (m?.videoEle) {
@@ -256,6 +307,41 @@ export default defineComponent({
       }
     }
 
+    //当前视频播放完
+    const handleEnded = (m: any) => {
+      // console.log(m)
+      // 自动播放下一个知识点
+      if (m.autoPlay) {
+        if (popupData.activeIndex != data.itemList.length - 1) {
+          popupData.activeIndex++
+          swipeRef.value?.next()
+          const nextItem = data.itemList[popupData.activeIndex]
+          nextTick(() => {
+            nextItem.videoEle?.play()
+          })
+          console.log('🚀 ~ nextItem', nextItem)
+        }
+      }
+    }
+
+    //加载第一帧
+    const handleFirstFrame = (video: HTMLVideoElement) => {
+      // console.log("🚀 ~ 加载第一帧", video.videoWidth, video.videoHeight)
+      const captureImage = function () {
+        var canvas = document.createElement('canvas')
+        canvas.width = video.videoWidth
+        canvas.height = video.videoHeight
+        canvas?.getContext('2d')?.drawImage(video, 0, 0, canvas.width, canvas.height)
+        canvas.toBlob((blob) => {
+          // console.log("🚀 ~ blob", blob)
+          const imgUrl = URL.createObjectURL(blob as any)
+          // console.log("🚀 ~ imgUrl", imgUrl)
+          video.setAttribute('poster', imgUrl)
+        })
+      }
+      captureImage()
+    }
+
     return () => (
       <div class={styles.coursewarePlay}>
         <Swipe
@@ -265,6 +351,7 @@ export default defineComponent({
           loop={false}
           vertical
           lazyRender={true}
+          initialSwipe={popupData.firstIndex}
           onChange={handleSwipeChange}
         >
           {data.itemList.map((m: any, mIndex: number) => {
@@ -289,38 +376,41 @@ export default defineComponent({
                     {m.type === 'VIDEO' ? (
                       <>
                         <video
-                          preload='auto'
+                          playsinline="false"
+                          preload="auto"
                           class="player"
+                          poster={iconVideobg}
                           data-vid={m.id}
                           src={m.content}
                           loop={m.loop}
+                          // onLoadeddata={(e: Event) =>
+                          //   handleFirstFrame(e.target as unknown as HTMLVideoElement)
+                          // }
                           onLoadedmetadata={(e: Event) => {
                             const videoEle = e.target as unknown as HTMLVideoElement
-                            // console.log('video加载成功', videoEle)
-                            videoEle.currentTime = .5
                             m.currentTime = videoEle.currentTime
                             m.duration = videoEle.duration
                             m.videoEle = videoEle
                           }}
                           onTimeupdate={(e: Event) => {
                             const videoEle = e.target as unknown as HTMLVideoElement
-                            // console.log('video播放中')
                             m.currentTime = videoEle.currentTime
                           }}
                           onPlay={() => {
-                            console.log('播放')
+                            // 播放
                             m.paused = false
                           }}
                           onPause={() => {
+                            //暂停
                             clearTimeout(m.timer)
-                            console.log('暂停')
                             m.paused = true
                           }}
+                          onEnded={() => handleEnded(m)}
                         >
                           <source src={m.content} type="video/mp4" />
                         </video>
                         <Transition name="bottom">
-                          {activeData.model && (
+                          {m.playModel && (
                             <div class={styles.bottomFixedContainer}>
                               <div class={styles.time}>
                                 <span>{getSecondRPM(m.currentTime)}</span>
@@ -345,7 +435,7 @@ export default defineComponent({
                                       onClick={(e: Event) => {
                                         e.stopPropagation()
                                         clearTimeout(m.timer)
-                                        console.log('点击播放', m.videoEle)
+                                        closeToast()
                                         m.videoEle?.play()
                                         m.paused = false
                                         m.timer = setTimeout(() => {
@@ -382,7 +472,7 @@ export default defineComponent({
                                     />
                                   )}
                                 </div>
-                                <div>{popupData.itemName}</div>
+                                <div>{m.name}</div>
                               </div>
                             </div>
                           )}
@@ -391,7 +481,13 @@ export default defineComponent({
                     ) : m.type === 'IMG' ? (
                       <img src={m.content} />
                     ) : (
-                      <MusicScore music={m} />
+                      <MusicScore
+                        data-vid={m.id}
+                        music={m}
+                        onSetIframe={(el: any) => {
+                          m.iframeRef = el
+                        }}
+                      />
                     )}
                     {/* <Transition name="van-fade">
                       {activeData.model && <div class={styles.playModel}></div>}
@@ -405,7 +501,7 @@ export default defineComponent({
 
         <Transition name="top">
           {activeData.model && (
-            <div class={styles.headerContainer}>
+            <div class={styles.headerContainer} ref={headeRef}>
               <div class={styles.backBtn} onClick={() => goback()}>
                 <Icon name={iconBack} />
                 返回
@@ -444,20 +540,21 @@ export default defineComponent({
         <Transition name="left">
           {activeData.model && (
             <div class={styles.leftFixedBtns}>
-              <div class={[styles.fullBtn, styles.prePoint]} onClick={() => handlePreAndNext('up')}>
-                <span style={{ textAlign: 'center' }}>
-                  上一
-                  <br />
-                  知识点
-                </span>
-              </div>
-              <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
-                <span style={{ textAlign: 'center' }}>
-                  下一
-                  <br />
-                  知识点
-                </span>
-              </div>
+              {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>
@@ -477,6 +574,7 @@ export default defineComponent({
               // console.log(res)
               popupData.tabActive = res.tabActive
               popupData.itemActive = res.itemActive
+              popupData.tabName = res.tabName
               popupData.open = false
               toggleMaterial()
             }}

+ 184 - 2
src/views/exercise-after-class/index.module.less

@@ -1,4 +1,186 @@
-.exercise-after-class{
+.coursewarePlay {
+    position: relative;
+    height: 100vh;
+    background-color: rgba(89, 98, 126, 0.2);
+  }
+  .playModel {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    box-shadow: inset 0px 0px 164px 0px rgba(0, 0, 0, 1);
+    pointer-events: none;
+  }
+  .headerContainer {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 1;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+    color: #fff;
+    font-size: 12px;
+    background: linear-gradient(180deg, rgba(0, 0, 0, .6), transparent);
+  }
+  .backBtn {
+    color: #fff;
+    width: 40px;
+    height: 26px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    z-index: 10;
+  }
+  .menu {
+    flex: 1;
+    display: flex;
+    justify-content: center;
+    color: #fff;
+  }
+  .tabsContent {
     width: 100vw;
     height: 100vh;
-}
+    :global {
+      .van-tabs__wrap {
+        display: none !important;
+      }
+      .van-tabs__content {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+  .itemDiv {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    video {
+      width: 100%;
+      height: 100%;
+    }
+    img {
+      display: block;
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+  }
+  .rightFixedBtns {
+    position: fixed;
+    top: 50%;
+    transform: translateY(-50%);
+    right: 20px;
+    .point {
+      margin-top: 10px;
+      border-bottom-left-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+    .point + .fullBtn {
+      border-top-left-radius: 0;
+      border-top-right-radius: 0;
+    }
+  }
+  .leftFixedBtns {
+    position: fixed;
+    top: 50%;
+    transform: translateY(-50%);
+    left: 20px;
+    .prePoint {
+      margin-bottom: 8px;
+    }
+  }
+  .fullBtn {
+    width: 38px;
+    height: 55px;
+    background: rgba(51, 51, 51, 0.15);
+    border-radius: 8px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    color: #fff;
+    justify-content: space-evenly;
+    &:active {
+      opacity: 0.8;
+    }
+  }
+  .bottomFixedContainer {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 10;
+    background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+    .time {
+      display: flex;
+      justify-content: space-between;
+      color: #fff;
+      font-size: 10px;
+      padding: 4px 10px;
+    }
+    .slider {
+      padding: 8px 10px;
+    }
+    .actions {
+      display: flex;
+      justify-content: space-between;
+      color: #fff;
+      font-size: 12px;
+      padding: 8px 10px;
+      align-items: center;
+      :global {
+        .van-icon {
+          font-size: 20px;
+          margin-right: 14px;
+        }
+      }
+    }
+  }
+  .popup {
+    background: rgba(0, 0, 0, 0.5);
+  }
+  .overlayClass {
+    --van-overlay-background: transparent;
+  }
+  :global {
+    .top-enter-active,
+    .top-leave-active {
+      transition: transform 0.5s;
+    }
+    .top-enter-from,
+    .top-leave-to {
+      transform: translateY(-100%);
+    }
+  
+    .left-enter-active,
+    .left-leave-active {
+      transition: all 0.5s;
+    }
+    .left-enter-from,
+    .left-leave-to {
+      left: -60px;
+    }
+  
+    .right-enter-active,
+    .right-leave-active {
+      transition: all 0.5s;
+    }
+  
+    .right-enter-from,
+    .right-leave-to {
+      right: -60px;
+    }
+  
+    .bottom-enter-active,
+    .bottom-leave-active {
+      transition: transform 0.5s;
+    }
+  
+    .bottom-enter-from,
+    .bottom-leave-to {
+      transform: translateY(100%);
+    }
+  }
+  

+ 400 - 60
src/views/exercise-after-class/index.tsx

@@ -1,25 +1,110 @@
+import {
+  closeToast,
+  Icon,
+  Popup,
+  showConfirmDialog,
+  showToast,
+  Slider,
+  Swipe,
+  SwipeItem
+} from 'vant'
+import {
+  defineComponent,
+  onMounted,
+  reactive,
+  nextTick,
+  onUnmounted,
+  ref,
+  watch,
+  Transition,
+  computed
+} from 'vue'
+import styles from './index.module.less'
+import 'plyr/dist/plyr.css'
 import request from '@/helpers/request'
-import { browser } from '@/helpers/utils'
 import { state } from '@/state'
-import Plyr from 'plyr'
-import 'plyr/dist/plyr.css'
-import { NoticeBar } from 'vant'
-import { defineComponent, onMounted, reactive, nextTick, computed } from 'vue'
-import { useRoute } from 'vue-router'
-import styles from './index.module.less'
+import { useRoute, useRouter } from 'vue-router'
+import iconBack from '../coursewarePlay/image/back.svg'
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
+import iconLoop from '../coursewarePlay/image/icon-loop.svg'
+import iconLoopActive from '../coursewarePlay/image/icon-loop-active.svg'
+import iconplay from '../coursewarePlay/image/icon-play.svg'
+import iconpause from '../coursewarePlay/image/icon-pause.svg'
+import iconVideobg from '../coursewarePlay/image/icon-videobg.png'
+import { browser, getSecondRPM } from '@/helpers/utils'
+
+const materialType = {
+  视频: 'VIDEO',
+  图片: 'IMG',
+  曲目: 'SONG'
+}
+
 export default defineComponent({
   name: 'exercise-after-class',
   setup() {
+    const handleInit = (type = 0) => {
+      // 横屏
+      postMessage({
+        api: 'setRequestedOrientation',
+        content: {
+          orientation: type
+        }
+      })
+      // 头,包括返回箭头
+      postMessage({
+        api: 'setTitleBarVisibility',
+        content: {
+          status: type
+        }
+      })
+      // 安卓的状态栏
+      postMessage({
+        api: 'setStatusBarVisibility',
+        content: {
+          isVisibility: type
+        }
+      })
+    }
+    handleInit()
+    onUnmounted(() => {
+      handleInit(1)
+    })
+
     const route = useRoute()
+    const router = useRouter()
+    const query = route.query
+    const browserInfo = browser()
+    const headeRef = ref()
     const data = reactive({
+      videoData: null as any,
+      details: [] as any,
+      trainingTimes: 0,
+      itemList: [] as any,
+      showHead: true,
       loading: true,
-      recordLoading: false,
-      video: '',
-      currentTime: 0,
-      duration: 0
+      recordLoading: false
+    })
+    const activeData = reactive({
+      nowTime: 0,
+      model: true, // 遮罩
+      timer: null as any,
+      item: null as any
     })
-    console.log(route.query)
-    const getLessonTraining = async () => {
+    // 获取缓存路径
+    const getCacheFilePath = async (material: any) => {
+      const res = await promisefiyPostMessage({
+        api: 'getCourseFilePath',
+        content: {
+          url: material.content,
+          localPath: '',
+          materialId: material.id,
+          updateTime: material.updateTime,
+          type: material.type
+        }
+      })
+      return res
+    }
+    const getDetail = async () => {
       let details = []
       try {
         const res: any = await request.get(
@@ -28,62 +113,118 @@ export default defineComponent({
         if (Array.isArray(res?.data)) {
           const studentLevel = state.user?.data?.studentLevel || 1
           details = res.data.find((n: any) => n.studentLevel === studentLevel)?.details || []
-          console.log('🚀 ~ details', details)
         }
       } catch (error) {
         console.log('error')
       }
       if (details.length) {
-        data.video =
-          (details.find((n: any) => n.materialId == route.query.materialId) as any)?.content || ''
-        console.log('🚀 ~ data.video', data.video)
-        nextTick(() => {
-          initVideo()
+        data.details = details
+        const videoData: any =
+          details.find((n: any) => n.materialId == route.query.materialId) || {}
+        try {
+          videoData.training = JSON.parse(videoData?.lessonTrainingTemp?.trainingConfigJson)
+        } catch (error) {}
+        //请求本地缓存
+        if (browserInfo.isApp && ['VIDEO'].includes(videoData.type)) {
+          const localData = await getCacheFilePath(videoData)
+          if (localData?.content?.localPath) {
+            videoData.url = videoData.content
+            videoData.content = localData.content.localPath
+          }
+        }
+        data.itemList.push({
+          ...videoData,
+          id: videoData.materialId,
+          currentTime: 0,
+          duration: 100,
+          paused: true,
+          loop: false,
+          videoEle: null,
+          timer: null,
+          playModel: true
         })
+        popupData.itemActive = videoData.id
+        popupData.tabName = videoData.materialName
+        data.videoData = videoData
+        handleExerciseCompleted()
       }
     }
-    const initVideo = () => {
-      const player = new Plyr('#player', {
-        clickToPlay: true,
-        invertTime: true,
-        controls: ['play-large', 'play', 'current-time', 'restart', 'fullscreen']
-      })
-      player.on('loadeddata', () => {
-        data.duration = player.duration
-        console.log('🚀 ~ player', player.duration)
-        data.loading = false
-      })
-      player.on('timeupdate', () => {
-        data.currentTime = player.currentTime
-        // console.log(timeRemaining.value)
-        if (timeRemaining.value === 0) {
-          if (data.recordLoading || data.currentTime === 0) return
-          console.log('完成观看次数')
-          addTrainingRecord()
+    const getTrainingTimes = (res: any) => {
+      let trainingTimes = 0
+      if (Array.isArray(res?.trainings)) {
+        const train = res.trainings.find((n: any) => n.materialId === query.materialId)
+        if (train) {
+          trainingTimes = train.trainingTimes
         }
-      })
+      }
+      data.trainingTimes = trainingTimes
+    }
+    // 获取课后练习记录
+    const trainingRecord = async () => {
+      try {
+        const res: any = await request.post(
+          state.platformApi +
+            `/studentLessonTraining/trainingRecord/${query.courseScheduleId}?userId=${state.user?.data?.id}`
+        )
+        if (res?.data) {
+          getTrainingTimes(res.data)
+          handleExerciseCompleted()
+        }
+      } catch (error) {}
     }
-    const timeRemaining = computed(() => {
-      return Math.ceil(data.duration - data.currentTime)
-    })
     onMounted(() => {
-      getLessonTraining()
+      getDetail()
+      trainingRecord()
     })
+    // 返回
+    const goback = () => {
+      postMessage({ api: 'back' })
+    }
+
+    const swipeRef = ref()
+    const popupData = reactive({
+      firstIndex: 0,
+      open: false,
+      activeIndex: -1,
+      tabActive: '',
+      tabName: '',
+      itemActive: '',
+      itemName: ''
+    })
+
+    // 双击
+    const handleDbClick = (item: any) => {
+      if (item && item.type === 'VIDEO') {
+        const videoEle: HTMLVideoElement = document.querySelector(`[data-vid='${item.id}']`)!
+        if (videoEle) {
+          if (videoEle.paused) {
+            closeToast()
+            videoEle.play()
+          } else {
+            showToast('已暂停')
+            videoEle.pause()
+          }
+        }
+        item.timer = setTimeout(() => {
+          activeData.model = false
+        }, 3000)
+      }
+    }
+
     // 达到指标,记录
-    const addTrainingRecord = async () => {
+    const addTrainingRecord = async (m: any) => {
       data.recordLoading = true
-      const browserInfo = browser()
       const body = {
         materialType: 'VIDEO',
         record: {
-          sourceTime: data.duration,
+          sourceTime: m.duration,
           clientType: state.platformType,
           feature: 'LESSON_TRAINING',
           deviceType: browserInfo.android ? 'ANDROID' : browserInfo.isApp ? 'IOS' : 'WEB'
         },
-        courseScheduleId: route.query.courseScheduleId,
-        lessonTrainingId: route.query.lessonTrainingId,
-        materialId: route.query.materialId
+        courseScheduleId: query.courseScheduleId,
+        lessonTrainingId: query.lessonTrainingId,
+        materialId: query.materialId
       }
       try {
         const res: any = await request.post(
@@ -92,21 +233,220 @@ export default defineComponent({
             data: body
           }
         )
+        trainingRecord()
       } catch (error) {}
-      data.recordLoading = false
+      setTimeout(() => {
+        data.recordLoading = false
+      }, 2000)
+    }
+    // 停止所有的播放
+    const handleStopVideo = () => {
+      data.itemList.forEach((m: any) => {
+        m.videoEle?.pause()
+      })
+    }
+
+    // 判断练习是否完成
+    const handleExerciseCompleted = () => {
+      if (
+        data.trainingTimes != 0 &&
+        data.trainingTimes == (data.videoData as any)?.training?.practiceTimes
+      ) {
+        handleStopVideo()
+        const itemIndex = data.details.findIndex(
+          (n: any) => n.materialId == data.videoData?.materialId
+        )
+        const isLastIndex = itemIndex === data.details.length - 1
+        showConfirmDialog({
+          title: '课后训练',
+          message: '你已完成该练习~',
+          confirmButtonColor: 'var(--van-primary)',
+          confirmButtonText: isLastIndex ? '完成' : '下一题',
+          cancelButtonText: '继续'
+        }).then(() => {
+          if (!isLastIndex) {
+            const nextItem = data.details[itemIndex + 1]
+            if (nextItem?.type === materialType.视频) {
+              // console.log('下一题视频', nextItem)
+              router.replace({
+                path: '/exerciseAfterClass',
+                query: {
+                  ...query,
+                  materialId: nextItem.materialId
+                }
+              })
+            }
+            if (nextItem?.type === materialType.曲目) {
+              goback()
+              let src = `${location.origin}/orchestra-music-score/?id=${nextItem.content}`
+              postMessage({
+                api: 'openAccompanyWebView',
+                content: {
+                  url: src,
+                  orientation: 0,
+                  isHideTitle: true,
+                  statusBarTextColor: false,
+                  isOpenLight: true
+                }
+              })
+            }
+          }
+        })
+      }
     }
     return () => (
-      <div class={styles['exercise-after-class']}>
-        <video id="player" src={data.video}></video>
-        {!data.loading && (
-          <>
-            {timeRemaining.value === 0 ? (
-              <NoticeBar background="green" color="#fff" text={`已完成本次训练`} />
-            ) : (
-              <NoticeBar text={`还需要观看 ${timeRemaining.value} 秒,就可以完成课后训练了`} />
-            )}
-          </>
-        )}
+      <div class={styles.coursewarePlay}>
+        <Swipe
+          style={{ height: '100vh' }}
+          ref={swipeRef}
+          showIndicators={false}
+          loop={false}
+          vertical
+          lazyRender={true}
+        >
+          {data.itemList.map((m: any, mIndex: number) => {
+            return (
+              <SwipeItem>
+                <>
+                  <div
+                    class={styles.itemDiv}
+                    onClick={() => {
+                      clearTimeout(activeData.timer)
+                      clearTimeout(m.timer)
+                      if (Date.now() - activeData.nowTime < 300) {
+                        handleDbClick(m)
+                        return
+                      }
+                      activeData.nowTime = Date.now()
+                      activeData.timer = setTimeout(() => {
+                        activeData.model = !activeData.model
+                      }, 300)
+                    }}
+                  >
+                    <video
+                      playsinline="false"
+                      preload="auto"
+                      class="player"
+                      poster={iconVideobg}
+                      data-vid={m.id}
+                      src={m.content}
+                      loop={m.loop}
+                      onLoadedmetadata={(e: Event) => {
+                        const videoEle = e.target as unknown as HTMLVideoElement
+                        m.currentTime = videoEle.currentTime
+                        m.duration = videoEle.duration
+                        m.videoEle = videoEle
+                      }}
+                      onTimeupdate={(e: Event) => {
+                        const videoEle = e.target as unknown as HTMLVideoElement
+                        m.currentTime = videoEle.currentTime
+                        if (m.duration - m.currentTime < 1) {
+                          if (data.recordLoading) return
+                          console.log('完成观看次数')
+                          addTrainingRecord(m)
+                        }
+                      }}
+                      onPlay={() => {
+                        // 播放
+                        m.paused = false
+                      }}
+                      onPause={() => {
+                        //暂停
+                        clearTimeout(m.timer)
+                        m.paused = true
+                      }}
+                    >
+                      <source src={m.content} type="video/mp4" />
+                    </video>
+                    <Transition name="bottom">
+                      {activeData.model && (
+                        <div class={styles.bottomFixedContainer}>
+                          <div class={styles.time}>
+                            <span>{getSecondRPM(m.currentTime)}</span>
+                            <span>{getSecondRPM(m.duration)}</span>
+                          </div>
+                          <div class={styles.slider}>
+                            <Slider
+                              buttonSize={16}
+                              step={0.01}
+                              modelValue={m.currentTime}
+                              min={0}
+                              max={m.duration}
+                            />
+                          </div>
+
+                          <div class={styles.actions}>
+                            <div>
+                              {m.paused ? (
+                                <Icon
+                                  name={iconplay}
+                                  onClick={(e: Event) => {
+                                    e.stopPropagation()
+                                    clearTimeout(m.timer)
+                                    closeToast()
+                                    m.videoEle?.play()
+                                    m.paused = false
+                                    m.timer = setTimeout(() => {
+                                      activeData.model = false
+                                    }, 3000)
+                                  }}
+                                />
+                              ) : (
+                                <Icon
+                                  name={iconpause}
+                                  onClick={(e: Event) => {
+                                    e.stopPropagation()
+                                    console.log('点击暂停')
+                                    m.videoEle?.pause()
+                                    m.paused = true
+                                  }}
+                                />
+                              )}
+                              {m.loop ? (
+                                <Icon
+                                  name={iconLoopActive}
+                                  onClick={(e: Event) => {
+                                    e.stopPropagation()
+                                    m.loop = false
+                                  }}
+                                />
+                              ) : (
+                                <Icon
+                                  name={iconLoop}
+                                  onClick={(e: Event) => {
+                                    e.stopPropagation()
+                                    m.loop = true
+                                  }}
+                                />
+                              )}
+                            </div>
+                            <div>{m.name}</div>
+                          </div>
+                        </div>
+                      )}
+                    </Transition>
+                  </div>
+                </>
+              </SwipeItem>
+            )
+          })}
+        </Swipe>
+
+        <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 class={styles.nums}>
+                练习次数:{data.trainingTimes}/
+                {(data.videoData as any)?.training?.practiceTimes || 0}
+              </div>
+            </div>
+          )}
+        </Transition>
       </div>
     )
   }

+ 3 - 3
src/views/exercise-record/index.tsx

@@ -171,13 +171,13 @@ export default defineComponent({
 
     const getSubjects = async () => {
       try {
-        const res = await request.post(`${platformApi.value}/subject/page`, {
+        const res = await request.post(`${platformApi.value}/subjectBasicConfig/page`, {
           data: { page: 1, rows: 9999 }
         })
         state.subjects = res.data.rows.map((item) => {
           return {
-            name: item.name,
-            value: item.id as string
+            name: item.subjectName,
+            value: item.subjectId as string
           }
         })
         state.subjects.unshift({ name: '全部声部', value: '' })

+ 1 - 1
src/views/information/help-center/index.tsx

@@ -88,7 +88,7 @@ export default defineComponent({
             ))}
           </List>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无数据" />
+          <OEmpty btnStatus={false} tips="暂无数据" />
         )}
       </>
     )

+ 1 - 0
src/views/layout/auth.tsx

@@ -100,6 +100,7 @@ export default defineComponent({
               tips="加载失败,请稍后重试"
               buttonText="重新加载"
               plain={true}
+              btnStatus={true}
               onClick={this.setAuth}
             />
           </div>

+ 3 - 3
src/views/layout/login.tsx

@@ -65,15 +65,15 @@ export default defineComponent({
           client_id: state.clientId[state.platformType],
           client_secret: state.clientId[state.platformType]
         }
-          console.log("🚀 ~ state.clientId", state.clientId,state.platformType)
+        console.log('🚀 ~ state.clientId', state.clientId, state.platformType)
         if (this.loginType === 'PWD') {
           forms.password = this.password
           forms.loginType = 'PASSWORD'
-          forms.grant_type = 'PASSWORD'
+          forms.grant_type = 'password'
         } else {
           forms.password = this.smsCode
           forms.loginType = 'SMS'
-          forms.grant_type = 'SMS'
+          forms.grant_type = 'password'
         }
         const { data } = await request.post('/api-oauth/userlogin', {
           requestType: 'form',

+ 4 - 1
src/views/lessonCourseware/index.tsx

@@ -1,6 +1,6 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Empty, Grid, GridItem, Icon, showToast, Toast } from 'vant'
+import { Button, Empty, Grid, GridItem, Icon, showToast, Toast } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import styles from './index.module.less'
 import iconLook from './image/look.svg'
@@ -91,6 +91,9 @@ export default defineComponent({
             )
           })}
         </Grid>
+        <Button onClick={() => {
+          location.href = 'http://192.168.3.114:1000/teacher.html#/courseList?id=1610595624868495362'
+        }}>胜强测试</Button>
         {!data.loading && !data.list.length && <OEmpty tips="没有课件" />}
       </div>
     )

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


BIN
src/views/unit-test/images/icon-button-list.png


BIN
src/views/unit-test/images/icon-count-down.png


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


BIN
src/views/unit-test/images/icon-question-nums.png


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


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

@@ -0,0 +1,68 @@
+.unitTest {
+  .searchBand {
+    display: inline-block;
+    font-size: 14px;
+    font-weight: 500;
+    color: #333333;
+  }
+
+  .cellGroup {
+    margin-bottom: 12px;
+    :global {
+      .van-cell {
+        padding: 12px;
+        font-size: 16px;
+        font-weight: 500;
+      }
+    }
+
+    .img {
+      width: 18px;
+      height: 18px;
+      margin-right: 6px;
+    }
+    .unitTitle {
+      color: #333333;
+      max-width: 150px;
+      line-height: 22px;
+    }
+
+    .no-start {
+      color: #f44541;
+    }
+    .pass {
+      color: #4ab78e;
+    }
+  }
+
+  .unitSection {
+    padding-top: 15px !important;
+    padding-bottom: 20px !important;
+    .name {
+      font-size: 15px;
+      font-weight: 500;
+      color: #333333;
+      padding-bottom: 6px;
+    }
+    .endTime {
+      font-size: 13px;
+      color: #777777;
+    }
+  }
+
+  .unitBtnGroup {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-top: 16px;
+    :global {
+      .van-button {
+        line-height: 40px;
+        height: 40px;
+        & + .van-button {
+          margin-left: 13px;
+        }
+      }
+    }
+  }
+}

+ 168 - 0
src/views/unit-test/index.tsx

@@ -0,0 +1,168 @@
+import OEmpty from '@/components/o-empty'
+import OHeader from '@/components/o-header'
+import OSearch from '@/components/o-search'
+import OSticky from '@/components/o-sticky'
+import { ActionSheet, Button, Cell, CellGroup, Icon, Image, List } from 'vant'
+import { defineComponent, reactive } from 'vue'
+import styles from './index.module.less'
+import iconEdit from './images/icon-edit.png'
+import { useRouter } from 'vue-router'
+
+export default defineComponent({
+  name: 'unit-test',
+  setup() {
+    const router = useRouter()
+    const form = reactive({
+      oPopover: false,
+      list: [] as any,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false
+      },
+      statusText: '全部测验',
+      params: {
+        keyword: null,
+        status: null,
+        page: 1,
+        rows: 20
+      },
+      isClick: false
+    })
+
+    const getList = async () => {
+      try {
+        if (form.isClick) return
+        form.isClick = true
+        // const res = await request.post('/api-school/schoolStaff/page', {
+        //   data: {
+        //     ...form.params,
+        //     schoolId: state.user.data.school.id
+        //   }
+        // })
+        // form.listState.loading = false
+        // const result = res.data || {}
+        // // 处理重复请求数据
+        // if (form.list.length > 0 && result.current === 1) {
+        //   return
+        // }
+        // form.list = form.list.concat(result.rows || [])
+        // form.listState.finished = result.current >= result.pages
+        // form.params.page = result.current + 1
+        // form.listState.dataShow = form.list.length > 0
+        form.isClick = false
+      } catch {
+        form.listState.dataShow = false
+        form.listState.finished = true
+        form.isClick = false
+      }
+    }
+
+    const onSearch = () => {
+      form.params.page = 1
+      form.list = []
+      form.listState.dataShow = true // 判断是否有数据
+      form.listState.loading = false
+      form.listState.finished = false
+      getList()
+    }
+    return () => (
+      <div class={styles.unitTest}>
+        <OSticky position="top">
+          <OSearch
+            placeholder="请输入测验名称"
+            inputBackground="white"
+            background="#f6f8f9"
+            onSearch={(val: any) => {
+              form.params.keyword = val
+              onSearch()
+            }}
+            v-slots={{
+              left: () => (
+                <div
+                  class={styles.searchBand}
+                  style={{ marginRight: '13px' }}
+                  onClick={() => (form.oPopover = true)}
+                >
+                  {form.statusText} <Icon name={form.oPopover ? 'arrow-up' : 'arrow-down'} />
+                </div>
+              )
+            }}
+          />
+        </OSticky>
+
+        {form.listState.dataShow ? (
+          <List
+            v-model:loading={form.listState.loading}
+            finished={form.listState.finished}
+            finishedText=" "
+            class={[styles.liveList]}
+            onLoad={getList}
+            immediateCheck={false}
+          >
+            {[1, 2, 3, 4].map((item: any) => (
+              <CellGroup inset class={styles.cellGroup} border={false}>
+                <Cell center isLink clickable={false}>
+                  {{
+                    icon: () => <Image src={iconEdit} class={styles.img} />,
+                    title: () => (
+                      <span class={[styles.unitTitle, 'van-ellipsis']}>长笛班-第一次单元测验</span>
+                    ),
+                    value: () => <span class={styles['no-start']}>未完成</span>
+                  }}
+                </Cell>
+                <Cell center class={styles.unitSection}>
+                  {{
+                    title: () => (
+                      <div class={styles.unitInformation}>
+                        <div class={styles.name}>武汉小学2022标准团</div>
+                        <div class={styles.endTime}>截止时间:2022-10-24 21:00</div>
+
+                        <div class={styles.unitBtnGroup}>
+                          <Button color="#FFF0E6" round block style={{ color: '#F67146' }}>
+                            练习模式
+                          </Button>
+                          <Button
+                            type="primary"
+                            round
+                            block
+                            onClick={() => {
+                              router.push('/unit-detail')
+                            }}
+                          >
+                            开始测验
+                          </Button>
+                        </div>
+                      </div>
+                    )
+                  }}
+                </Cell>
+              </CellGroup>
+            ))}
+          </List>
+        ) : (
+          <OEmpty tips="暂无单元测验" />
+        )}
+
+        <ActionSheet
+          v-model:show={form.oPopover}
+          cancelText="取消"
+          actions={
+            [
+              { name: '全部测验', id: 'ALL' },
+              { name: '未完成', id: 'ING' },
+              { name: '不合格', id: 'LOCKED' },
+              { name: '合格', id: 'ACTIVATION' }
+            ] as any
+          }
+          onSelect={(val: any) => {
+            form.statusText = val.name
+            form.params.status = val.id === 'ALL' ? null : val.id
+            form.oPopover = false
+            onSearch()
+          }}
+        />
+      </div>
+    )
+  }
+})

+ 117 - 0
src/views/unit-test/model/notice-start/index.module.less

@@ -0,0 +1,117 @@
+.dialogTitle {
+  i {
+    display: inline-block;
+    width: 4px;
+    height: 14px;
+    background: #ff8057;
+    border-radius: 2px;
+    margin-right: 6px;
+  }
+
+  text-align: left;
+  font-size: 18px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 25px;
+  padding: 20px 15px 15px;
+}
+
+.btns {
+  :global {
+    .van-button {
+      font-size: 18px;
+      font-weight: 500;
+    }
+    .van-dialog__cancel {
+      color: #777;
+    }
+  }
+}
+
+.noticeContainer {
+  padding: 0 15px 30px;
+}
+
+.unitContainer {
+  background: #fff0e6;
+  border-radius: 10px;
+  margin-bottom: 30px;
+  .unitTitle {
+    display: flex;
+    align-items: center;
+    padding: 10px 12px;
+    font-size: 16px;
+    font-weight: 500;
+    line-height: 22px;
+    color: #333333;
+    :global {
+      .van-tag {
+        margin-left: 8px;
+        font-size: 13px;
+        font-weight: 500;
+        color: #ffffff;
+        padding: 2px 8px;
+        flex-shrink: 0;
+      }
+    }
+  }
+  .gridScore {
+    :global {
+      .van-grid-item__content {
+        background-color: transparent;
+      }
+    }
+    .title {
+      font-size: 26px;
+      font-weight: bold;
+      color: #333333;
+      line-height: 30px;
+    }
+    .name {
+      padding-top: 4px;
+      font-size: 14px;
+      color: #333333;
+    }
+  }
+
+  .unitTimer {
+    display: flex;
+    align-items: center;
+    padding: 12px;
+    font-size: 14px;
+    color: #333333;
+    line-height: 20px;
+    :global {
+      .van-icon {
+        font-size: 16px;
+        margin-right: 4px;
+      }
+    }
+    &:after {
+      border-color: #ff8057;
+      opacity: 0.2;
+    }
+  }
+}
+
+.reminder {
+  .iconBell {
+    width: 15px;
+    height: 16px;
+    margin-right: 6px;
+  }
+  .rTitle {
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    font-weight: 500;
+    color: #f67146;
+    padding-bottom: 10px;
+  }
+  .rContent {
+    font-size: 14px;
+    color: #333333;
+    line-height: 21px;
+    text-align: justify;
+  }
+}

+ 82 - 0
src/views/unit-test/model/notice-start/index.tsx

@@ -0,0 +1,82 @@
+import { Button, Grid, GridItem, Icon, Tag } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import iconBell from '@/views/unit-test/images/icon-bell.png'
+import iconTimer from '@/views/unit-test/images/icon-timer.png'
+
+export default defineComponent({
+  name: 'notice-start',
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    return () => (
+      <div class={styles.noticeStart}>
+        <div class={styles.dialogTitle}>
+          <i></i>
+          测验须知
+        </div>
+
+        <div class={styles.noticeContainer}>
+          <div class={styles.unitContainer}>
+            <div class={styles.unitTitle}>
+              <span class={[styles.name, 'van-ellipsis']}>长笛level1上册测验一</span>
+              <Tag type="primary">长笛单技课</Tag>
+            </div>
+            <Grid border={false} columnNum={3} class={styles.gridScore}>
+              <GridItem>
+                <p class={styles.title}>20</p>
+                <p class={styles.name}>总分</p>
+              </GridItem>
+              <GridItem>
+                <p
+                  class={[styles.title]}
+                  style={{
+                    color: '#F67146'
+                  }}
+                >
+                  16
+                </p>
+                <p class={styles.name}>合格分</p>
+              </GridItem>
+              <GridItem>
+                <p class={styles.title}>4</p>
+                <p class={styles.name}>题目数量</p>
+              </GridItem>
+            </Grid>
+
+            <div class={[styles.unitTimer, 'van-hairline--top']}>
+              <Icon name={iconTimer} />
+              <span>测验时长:40:00</span>
+            </div>
+          </div>
+
+          <div class={styles.reminder}>
+            <div class={styles.rTitle}>
+              <Icon name={iconBell} class={styles.iconBell} />
+              <span>温馨提示:</span>
+            </div>
+
+            <p class={styles.rContent}>
+              1、单元测验仅可进行一次,请通过练习模式充分练习后再进行测试,以保障测验分数准确;
+              <br />
+              2、点击开始测验后开始测验计时,到达测验时长后自动完成;
+              <br />
+              3、开始测验后若中途退出,时长依然计算; <br /> 4、准备好后开始测验吧!
+            </p>
+          </div>
+        </div>
+
+        <div class={['van-hairline--top van-dialog__footer', styles.btns]}>
+          <Button onClick={() => emit('close')} class={['van-dialog__cancel']}>
+            再等等
+          </Button>
+          <Button
+            onClick={() => emit('confirm')}
+            class={['van-dialog__confirm van-hairline--left']}
+          >
+            开始测验
+          </Button>
+        </div>
+      </div>
+    )
+  }
+})

+ 63 - 0
src/views/unit-test/unit-detail/index.module.less

@@ -0,0 +1,63 @@
+.unitDetail {
+  overflow: hidden;
+}
+
+.unitSection {
+  margin: 12px 13px;
+  padding: 13px 15px;
+  width: auto;
+  overflow: hidden;
+  border-radius: 10px;
+
+  .unitTitle {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+  }
+
+  .unitCount {
+    padding-top: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .qNums {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    color: #333333;
+    line-height: 20px;
+    .num {
+      color: #f67146;
+    }
+  }
+  .icon {
+    width: 14px;
+    height: 14px;
+    margin-right: 4px;
+  }
+}
+
+.wapList {
+  width: 44px;
+  height: 44px;
+  flex-shrink: 0;
+  margin-left: 18px;
+}
+
+.unitSubject {
+  padding: 15px;
+  margin: 0 13px;
+  background-color: #fff;
+  overflow: hidden;
+  border-radius: 10px;
+}
+.unitSubjectTitle {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 26px;
+  .unitScore {
+    color: #777777;
+  }
+}

+ 93 - 0
src/views/unit-test/unit-detail/index.tsx

@@ -0,0 +1,93 @@
+import { Button, Cell, Icon, Image, Popup, Swipe, SwipeItem, Tag } from 'vant'
+import { defineComponent, reactive, ref } 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'
+
+export default defineComponent({
+  name: 'unit-detail',
+  setup() {
+    const route = useRoute()
+    const router = useRouter()
+    const swipeRef = ref()
+    const state = reactive({
+      visiableNotice: false
+    })
+    return () => (
+      <div class={styles.unitDetail}>
+        <Cell center class={styles.unitSection}>
+          {{
+            title: () => <div class={styles.unitTitle}>长笛level1上册测验一</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} />
+                  剩余时长:39:30
+                </div>
+              </div>
+            )
+          }}
+        </Cell>
+
+        <Swipe loop={false} showIndicators={false} ref={swipeRef} duration={300}>
+          <SwipeItem>
+            <div class={styles.unitSubject}>
+              <div class={styles.unitSubjectTitle}>
+                1、选出与方框内音符时值相同的节奏阶段 <span class={styles.unitScore}>(5分)</span>
+                <Tag type="primary">单选题</Tag>
+              </div>
+              <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/dbb27307d428424c8efb9f26032cfa1a_mergeImage.png" />
+            </div>
+          </SwipeItem>
+          <SwipeItem>
+            <div class={styles.unitSubject}>
+              <Image src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/dbb27307d428424c8efb9f26032cfa1a_mergeImage.png" />
+            </div>
+          </SwipeItem>
+        </Swipe>
+
+        <OSticky position="bottom" background="white">
+          <div class={['btnGroup btnMore']}>
+            <Button
+              block
+              round
+              type="primary"
+              onClick={() => {
+                swipeRef.value.next()
+              }}
+            >
+              下一题
+            </Button>
+            <Image src={iconButtonList} class={[styles.wapList, 'van-haptics-feedback']} />
+          </div>
+        </OSticky>
+
+        {/* 测验须知 */}
+        <Popup
+          v-model:show={state.visiableNotice}
+          round
+          style={{ width: '90%' }}
+          closeOnClickOverlay={false}
+        >
+          <NoticeStart
+            onClose={() => {
+              state.visiableNotice = false
+              router.back()
+            }}
+            onConfirm={() => {
+              console.log('start')
+            }}
+          />
+        </Popup>
+      </div>
+    )
+  }
+})