瀏覽代碼

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

mo 2 年之前
父節點
當前提交
cb7788a62c
共有 79 個文件被更改,包括 3256 次插入403 次删除
  1. 19 26
      public/project/initiation.html
  2. 40 30
      public/project/preRegister.html
  3. 1 1
      public/project/schoolRegister.html
  4. 二進制
      src/common/images/icon_student.png
  5. 二進制
      src/common/images/icon_teacher.png
  6. 27 6
      src/components/o-dialog/index.tsx
  7. 6 8
      src/components/o-header/index.tsx
  8. 7 0
      src/components/o-protocol/index.module.less
  9. 4 2
      src/components/o-protocol/index.tsx
  10. 14 0
      src/constant/index.ts
  11. 20 4
      src/router/routes-common.ts
  12. 16 8
      src/school/companion-teacher/companion-teacher-register.tsx
  13. 56 2
      src/school/companion-teacher/index.tsx
  14. 8 0
      src/school/companion-teacher/unbind.module.less
  15. 11 3
      src/school/companion-teacher/unbind.tsx
  16. 2 2
      src/school/main.ts
  17. 1 0
      src/school/manage-teacher/index.module.less
  18. 60 6
      src/school/manage-teacher/index.tsx
  19. 1 0
      src/school/manage-teacher/manage-detail.module.less
  20. 24 7
      src/school/manage-teacher/manage-detail.tsx
  21. 11 4
      src/school/manage-teacher/manage-teacher-register.tsx
  22. 115 0
      src/school/mass-message/component/class-list/index.module.less
  23. 268 0
      src/school/mass-message/component/class-list/index.tsx
  24. 46 0
      src/school/mass-message/component/manage-list/index.module.less
  25. 200 0
      src/school/mass-message/component/manage-list/index.tsx
  26. 46 0
      src/school/mass-message/component/student-list/index.module.less
  27. 196 0
      src/school/mass-message/component/student-list/index.tsx
  28. 46 0
      src/school/mass-message/component/teacher-list/teacher-list.module.less
  29. 218 0
      src/school/mass-message/component/teacher-list/teacher-list.tsx
  30. 548 15
      src/school/mass-message/create-message.tsx
  31. 43 0
      src/school/mass-message/index.module.less
  32. 83 38
      src/school/mass-message/index.tsx
  33. 98 0
      src/school/mass-message/select-sned.tsx
  34. 1 0
      src/school/orchestra/compontent/information.module.less
  35. 3 1
      src/school/orchestra/compontent/information.tsx
  36. 2 2
      src/school/orchestra/index.tsx
  37. 1 0
      src/school/orchestra/modal/teacher-list.module.less
  38. 1 1
      src/school/orchestra/modal/teacher-list.tsx
  39. 3 1
      src/school/orchestra/orchestra-information.tsx
  40. 1 0
      src/school/train-planning/component/course-preview/index.module.less
  41. 33 18
      src/school/train-planning/component/course-preview/index.tsx
  42. 4 1
      src/school/train-planning/component/practice-detail/index.tsx
  43. 1 1
      src/school/train-planning/component/standard/index.tsx
  44. 12 0
      src/school/train-planning/modal/calendar/index.tsx
  45. 1 0
      src/school/train-planning/modal/class-list/index.module.less
  46. 1 1
      src/school/train-planning/modal/class-list/index.tsx
  47. 10 4
      src/school/train-planning/modal/practice-class/index.module.less
  48. 6 5
      src/school/train-planning/modal/practice-class/index.tsx
  49. 4 5
      src/school/train-planning/modal/timer/index.tsx
  50. 1 1
      src/state.ts
  51. 22 0
      src/student/main.ts
  52. 2 4
      src/student/member-center/index.tsx
  53. 15 5
      src/student/music-group/pre-apply/component/apply.tsx
  54. 1 1
      src/student/music-group/pre-apply/component/order.tsx
  55. 3 4
      src/student/music-group/pre-apply/component/payment.tsx
  56. 25 2
      src/student/music-group/pre-apply/index.tsx
  57. 17 7
      src/student/music-group/pre-apply/order-detail.tsx
  58. 29 5
      src/student/my-orchestra/apply-withdrawal.tsx
  59. 6 0
      src/student/my-orchestra/index.module.less
  60. 173 95
      src/student/my-orchestra/index.tsx
  61. 22 3
      src/student/payment-result/index.tsx
  62. 2 2
      src/student/trade-record/component/paid-list.tsx
  63. 30 4
      src/student/trade-record/component/wait-pay.tsx
  64. 1 1
      src/views/adapay/pay-define/index.tsx
  65. 2 0
      src/views/adapay/pay-result/index.tsx
  66. 67 16
      src/views/courseList/index.tsx
  67. 19 0
      src/views/coursewarePlay/component/musicScore.module.less
  68. 34 0
      src/views/coursewarePlay/component/musicScore.tsx
  69. 25 0
      src/views/coursewarePlay/component/point.module.less
  70. 51 0
      src/views/coursewarePlay/component/points.tsx
  71. 22 0
      src/views/coursewarePlay/image/icon-menu.svg
  72. 18 0
      src/views/coursewarePlay/image/icon-mulv.svg
  73. 62 21
      src/views/coursewarePlay/index.module.less
  74. 190 23
      src/views/coursewarePlay/index.tsx
  75. 2 1
      src/views/information/information-detail.tsx
  76. 39 0
      src/views/information/notice-detail.tsx
  77. 2 1
      src/views/lessonCourseware/index.module.less
  78. 14 5
      src/views/lessonCourseware/index.tsx
  79. 41 0
      src/views/preview-protocol/index.tsx

+ 19 - 26
public/project/initiation.html

@@ -209,7 +209,7 @@
           </template>
         </van-field>
         <van-field required label="在读年级" :disabled='checkPhone' v-model="stu.currentGrade" readonly name="currentGrade"
-          :rules="[{ required: true, message: '请选择在读年级', trigger: 'onChange' }]" @click="pickerChange('grade')"
+          :rules="[{ required: true, message: '请选择在读年级', trigger: 'onChange' }]" @click="() => showPicker = true"
           placeholder="请选择在读年级" clickable>
           <template #right-icon>
             <van-icon name="arrow" :color="checkPhone ? '#aaa' : '#323233'" size="16"></van-icon>
@@ -217,7 +217,7 @@
         </van-field>
         <van-field v-show="false" v-model="stu.currentGradeNum" name="currentGradeNum" readonly></van-field>
         <van-field required label="所在班级" :disabled='checkPhone' v-model="stu.currentClass" readonly name="currentClass"
-          :rules="[{ required: true, message: '请选择所在班级', trigger: 'onChange' }]" @click="pickerChange('class')"
+          :rules="[{ required: true, message: '请选择所在班级', trigger: 'onChange' }]" @click="() => classPicker = true"
           placeholder="请选择所在班级">
           <template #right-icon>
             <van-icon name="arrow" :color="checkPhone ? '#aaa' : '#323233'" size="16"></van-icon>
@@ -234,7 +234,8 @@
                 <van-radio class="radioItem" :name="1"></van-radio>是
               </van-tag>
               <van-tag size="large" type="primary" :plain="!(stu.hasLearningExperience === 0)"
-                :color="checkPhone ? '#ccc': '#FF8057'" class="radioSection">
+                :color="checkPhone ? '#ccc': '#FF8057'" class="radioSection"
+                @click="() => stu.learningSubjectName = ''">
                 <van-radio class="radioItem" :name="0"></van-radio>否
               </van-tag>
             </van-radio-group>
@@ -268,12 +269,16 @@
     </van-form>
 
     <van-popup v-model:show="showPicker" position="bottom" round>
-      <van-picker show-toolbar :columns="columns" @cancel="showPicker = false" @confirm="onConfirm" />
+      <van-picker show-toolbar :columns="currentGrade" @cancel="showPicker = false" @confirm="onConfirm" />
+    </van-popup>
+
+    <van-popup v-model:show="classPicker" position="bottom" round>
+      <van-picker show-toolbar :columns="classList" @cancel="classPicker = false" @confirm="onConfirmClass" />
     </van-popup>
 
     <van-popup v-model:show="submitStatus" round style="width: 75%" :close-on-click-overlay="false">
       <div class="submit-container">
-        <img class="icon-close" src="./images/initiation/icon-close.png" @click="onLinkUrl" />
+        <!-- <img class="icon-close" src="./images/initiation/icon-close.png" @click="onLinkUrl" /> -->
         <img src="./images/initiation/popup-submit.png" class="submit-img" />
         <p class="submit-tips">提交成功,感谢您的参与!</p>
         <van-button type="primary" color="#FF8057" block round @click="onLinkUrl">确认</van-button>
@@ -299,6 +304,7 @@
           orchestraName: null,
           schoolId: null,
           showPicker: false,
+          classPicker: false,
           submitStatus: false,
           pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
           nameReg: /^[\u4E00-\u9FA5]+$/,
@@ -454,30 +460,17 @@
             vant.closeToast()
           }
         },
-        pickerChange(type) {
-          if (this.checkPhone) return
-          this.columns = []
-          var stu = this.stu
-          if (type == 'grade') {
-            this.columns = this.currentGrade
-            this.pickerType = type
-          } else if (type == 'class') {
-            this.columns = this.classList
-            this.pickerType = type
-          }
-          this.showPicker = true
-        },
         onConfirm(options) {
           var stu = this.stu
-          console.log(options)
-          if (this.pickerType == 'grade') {
-            stu.currentGrade = options.selectedOptions[0].text
-            stu.currentGradeNum = options.selectedOptions[0].value
-          } else if (this.pickerType == 'class') {
-            stu.currentClass = options.selectedOptions[0].text
-            stu.currentClassNum = options.selectedOptions[0].value
-          }
+          stu.currentGrade = options.selectedOptions[0].text
+          stu.currentGradeNum = options.selectedOptions[0].value
           this.showPicker = false
+        },
+        onConfirmClass(options) {
+          var stu = this.stu
+          stu.currentClass = options.selectedOptions[0].text
+          stu.currentClassNum = options.selectedOptions[0].value
+          this.classPicker = false
         }
       }
     })

+ 40 - 30
public/project/preRegister.html

@@ -209,14 +209,14 @@
           </template>
         </van-field>
         <van-field required label="在读年级" :disabled='checkPhone' v-model="stu.currentGrade" readonly name="currentGrade"
-          :rules="[{ required: true, message: '请选择在读年级', trigger: 'onChange' }]" @click="pickerChange('grade')"
+          :rules="[{ required: true, message: '请选择在读年级', trigger: 'onChange' }]" @click="pickerChange"
           placeholder="请选择在读年级" clickable>
           <template #right-icon>
             <van-icon name="arrow" :color="checkPhone ? '#aaa' : '#323233'" size="16"></van-icon>
           </template>
         </van-field>
         <van-field required label="所在班级" :disabled='checkPhone' v-model="stu.currentClass" readonly name="currentClass"
-          :rules="[{ required: true, message: '请选择所在班级', trigger: 'onChange' }]" @click="pickerChange('class')"
+          :rules="[{ required: true, message: '请选择所在班级', trigger: 'onChange' }]" @click="pickerChange1"
           placeholder="请选择所在班级">
           <template #right-icon>
             <van-icon name="arrow" :color="checkPhone ? '#aaa' : '#323233'" size="16"></van-icon>
@@ -227,7 +227,7 @@
       <van-cell-group inset class="cell-group">
         <van-field required label="选报声部" :disabled='checkPhone' v-model="stu.registerSubjectId" readonly
           name="registerSubjectId" :rules="[{ required: true, message: '请选择选报声部', trigger: 'onChange' }]"
-          @click="pickerChange('subject')" placeholder="请选择选报声部">
+          @click="pickerChange2" placeholder="请选择选报声部">
           <template #right-icon>
             <van-icon name="arrow" :color="checkPhone ? '#aaa' : '#323233'" size="16"></van-icon>
           </template>
@@ -273,12 +273,20 @@
     </van-form>
 
     <van-popup v-model:show="showPicker" position="bottom" round>
-      <van-picker show-toolbar :columns="columns" @cancel="showPicker = false" @confirm="onConfirm" />
+      <van-picker show-toolbar :columns="currentGrade" @cancel="showPicker = false" @confirm="onConfirm" />
+    </van-popup>
+
+    <van-popup v-model:show="classPicker" position="bottom" round>
+      <van-picker show-toolbar :columns="classList" @cancel="classPicker = false" @confirm="onConfirmClass" />
+    </van-popup>
+
+    <van-popup v-model:show="subjectPicker" position="bottom" round>
+      <van-picker show-toolbar :columns="subjectList" @cancel="subjectPicker = false" @confirm="onConfirmSubject" />
     </van-popup>
 
     <van-popup v-model:show="submitStatus" round style="width: 75%" :close-on-click-overlay="false">
       <div class="submit-container">
-        <img class="icon-close" src="./images/initiation/icon-close.png" @click="onLinkUrl" />
+        <!-- <img class="icon-close" src="./images/initiation/icon-close.png" @click="onLinkUrl" /> -->
         <img src="./images/initiation/popup-submit.png" class="submit-img" />
         <p class="submit-tips">提交成功,感谢您的参与!</p>
         <van-button type="primary" color="#9A64FF" block round @click="onLinkUrl">确认</van-button>
@@ -305,6 +313,8 @@
           orchestraName: null,
           schoolId: null,
           showPicker: false,
+          classPicker: false,
+          subjectPicker: false,
           submitStatus: false,
           pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
           nameReg: /^[\u4E00-\u9FA5]+$/,
@@ -348,10 +358,11 @@
           var schoolDetail = await axios.post('/api-student/open/school/detail', {
             orchestraId: this.orchestraId
           })
+          console.log(schoolDetail)
           if (schoolDetail.data.code === 200) {
             var schoolSystem = schoolDetail.data.data.schoolSystem || 'sixYearSystem'
             this.schoolId = schoolDetail.data.data.id
-            if (schoolDetail === 'sixYearSystem') {
+            if (schoolSystem === 'sixYearSystem') {
               this.currentGrade.push({ text: '六年级', value: 6 })
             }
           }
@@ -502,35 +513,34 @@
         },
         pickerChange(type) {
           if (this.checkPhone) return
-          this.columns = []
-          var stu = this.stu
-          if (type == 'grade') {
-            this.columns = this.currentGrade
-            this.pickerType = type
-          } else if (type == 'class') {
-            this.columns = this.classList
-            this.pickerType = type
-          } else if (type == 'subject') {
-
-            this.columns = this.subjectList
-            this.pickerType = type
-          }
           this.showPicker = true
         },
+        pickerChange1(type) {
+          if (this.checkPhone) return
+          this.classPicker = true
+        },
+        pickerChange2(type) {
+          if (this.checkPhone) return
+          this.subjectPicker = true
+        },
         onConfirm(options) {
           var stu = this.stu
-          // console.log(options)
-          if (this.pickerType == 'grade') {
-            stu.currentGrade = options.selectedOptions[0].text
-            stu.currentGradeNum = options.selectedOptions[0].value
-          } else if (this.pickerType == 'class') {
-            stu.currentClass = options.selectedOptions[0].text
-            stu.currentClassNum = options.selectedOptions[0].value
-          } else if (this.pickerType === 'subject') {
-            stu.registerSubjectId = options.selectedOptions[0].text
-            stu.registerSubjectNum = options.selectedOptions[0].value
-          }
+          stu.currentGrade = options.selectedOptions[0].text
+          stu.currentGradeNum = options.selectedOptions[0].value
           this.showPicker = false
+        },
+        onConfirmClass(options) {
+          var stu = this.stu
+          stu.currentClass = options.selectedOptions[0].text
+          stu.currentClassNum = options.selectedOptions[0].value
+          this.classPicker = false
+        },
+        onConfirmSubject(options) {
+          var stu = this.stu
+          stu.registerSubjectId = options.selectedOptions[0].text
+          stu.registerSubjectNum = options.selectedOptions[0].value
+
+          this.subjectPicker = false
         }
       }
     })

+ 1 - 1
public/project/schoolRegister.html

@@ -141,7 +141,7 @@
 
     <van-popup v-model:show="submitStatus" round style="width: 75%" :close-on-click-overlay="false">
       <div class="submit-container">
-        <img class="icon-close" src="./images/initiation/icon-close.png" @click="onLinkUrl" />
+        <!-- <img class="icon-close" src="./images/initiation/icon-close.png" @click="onLinkUrl" /> -->
         <img src="./images/initiation/popup-submit.png" class="submit-img" />
         <p class="submit-tips">提交成功,感谢您的参与!</p>
         <van-button type="primary" color="#64A9FF" block round @click="onLinkUrl">确认</van-button>

二進制
src/common/images/icon_student.png


二進制
src/common/images/icon_teacher.png


+ 27 - 6
src/components/o-dialog/index.tsx

@@ -1,5 +1,5 @@
 import { Dialog } from 'vant'
-import { defineComponent } from 'vue'
+import { defineComponent, PropType, reactive, watch } from 'vue'
 import styles from './index.module.less'
 
 export default defineComponent({
@@ -32,21 +32,42 @@ export default defineComponent({
     showCancelButton: {
       type: Boolean,
       default: false
+    },
+    messageAlign: {
+      type: String as PropType<'left' | 'center' | 'right'>,
+      default: 'center'
     }
   },
-  emits: ['cancel', 'confirm'],
+  emits: ['cancel', 'confirm', 'update:show'],
   setup(props, { slots, attrs, emit }) {
+    const state = reactive({
+      show: props.show || false
+    })
+
+    // 监听状态
+    watch(
+      () => props.show,
+      () => {
+        state.show = props.show
+      }
+    )
     return () => (
       <Dialog
-        v-model:show={props.show}
+        v-model:show={state.show}
         message={props.message}
-        messageAlign="left"
+        messageAlign={props.messageAlign}
         confirmButtonText={props.confirmButtonText}
         cancelButtonText={props.cancelButtonText}
         showConfirmButton={props.showConfirmButton}
         showCancelButton={props.showCancelButton}
-        onConfirm={() => emit('confirm')}
-        onCancel={() => emit('cancel')}
+        onConfirm={() => {
+          emit('update:show', false)
+          emit('confirm')
+        }}
+        onCancel={() => {
+          emit('update:show', false)
+          emit('cancel')
+        }}
       >
         {{
           title: () => (

+ 6 - 8
src/components/o-header/index.tsx

@@ -4,8 +4,6 @@ import { NavBar } from 'vant'
 import { defineComponent, PropType, Teleport } from 'vue'
 import styles from './index.module.less'
 
-type backIconColor = 'black' | 'white'
-
 export default defineComponent({
   name: 'o-header',
   props: {
@@ -16,7 +14,7 @@ export default defineComponent({
     },
     backIconColor: {
       // 返回按钮颜色
-      type: String as PropType<backIconColor>,
+      type: String as PropType<'black' | 'white'>,
       default: 'black'
     },
     isFixed: {
@@ -80,18 +78,18 @@ export default defineComponent({
   },
   unmounted() {
     if (this.desotry) {
-      // 设置是否显示导航栏 0 显示 1 显示
+      // 设置是否显示导航栏 0 显示 1 显示
       postMessage({ api: 'setBarStatus', content: { status: 1 } })
       // 设置返回按钮颜色
       postMessage({
         api: 'backIconChange',
-        content: { iconStyle: 'black' as backIconColor }
+        content: { iconStyle: 'black' }
       })
     }
   },
   methods: {
     navBarInit(callBack?: Function) {
-      // 设置是否显示导航栏 0 显示 1 显示
+      // 设置是否显示导航栏 0 显示 1 显示
       postMessage({ api: 'setBarStatus', content: { status: 0 } })
       // 设置返回按钮颜色
       postMessage({
@@ -160,12 +158,12 @@ export default defineComponent({
                 style={{
                   background: this.background,
                   color: this.color,
-                  paddingTop: `${this.navBarHeight}px`,
-                  zIndex: 99
+                  paddingTop: `${this.navBarHeight}px`
                 }}
                 left-arrow={this.isBack}
                 rightText={this.rightText}
                 fixed={this.isFixed}
+                zIndex={2000}
                 border={this.border}
                 onClick-right={() => this.clickRight()}
                 onClick-left={this.onClickLeft}

+ 7 - 0
src/components/o-protocol/index.module.less

@@ -31,4 +31,11 @@
       line-height: 15px;
     }
   }
+
+  .protocolContent {
+    font-size: 14px;
+    padding: 12px;
+    color: #333;
+    line-height: 1.4;
+  }
 }

+ 4 - 2
src/components/o-protocol/index.tsx

@@ -75,7 +75,9 @@ export default defineComponent({
           this.protocolHTML = data.contractTemplateContent
         }
         this.onPopupClose()
-      } catch {}
+      } catch {
+        //
+      }
     },
     onHash() {
       this.popupStatus = false
@@ -133,7 +135,7 @@ export default defineComponent({
         >
           {this.showHeader && <ColHeader title="管乐团平台服务协议" />}
           {this.popupStatus && (
-            <div class={styles.protocolContent} id="mProtocol">
+            <div id="mProtocol">
               <div class={styles.protocolContent} v-html={this.protocolHTML}></div>
             </div>
           )}

+ 14 - 0
src/constant/index.ts

@@ -126,3 +126,17 @@ export const orderType = {
   VIP: '开通会员',
   ORCHESTRA: '乐团报名'
 }
+
+// 发送方式
+export const sendType = {
+  IMMEDIATELY: '即时发送',
+  SCHEDULED: '定时发送'
+}
+
+// 发送状态
+export const snedStatus = {
+  WAIT: "待发送",
+  SEND: "已发送",
+  DISABLE: "已停用",
+  EXPIRE: "已失效",
+}

+ 20 - 4
src/router/routes-common.ts

@@ -26,10 +26,6 @@ export const router = [
       title: '课程播放'
     }
   },
-]
-
-// 不需要登录的路由
-export const rootRouter = [
   {
     path: '/information-detail',
     name: 'information-detail',
@@ -39,6 +35,18 @@ export const rootRouter = [
     }
   },
   {
+    path: '/notice-detail',
+    name: 'notice-detail',
+    component: () => import('@/views/information/notice-detail'),
+    meta: {
+      title: '公告详情'
+    }
+  }
+]
+
+// 不需要登录的路由
+export const rootRouter = [
+  {
     path: '/payCenter',
     name: 'payCenter',
     component: () => import('@/views/adapay/pay-center'),
@@ -63,6 +71,14 @@ export const rootRouter = [
     }
   },
   {
+    path: '/preview-protocol',
+    name: 'preview-protocol',
+    component: () => import('@/views/preview-protocol/index'),
+    meta: {
+      title: '管乐团用户注册协议'
+    }
+  },
+  {
     path: '/:pathMatch(.*)*',
     component: () => import('@/views/404'),
     meta: {

+ 16 - 8
src/school/companion-teacher/companion-teacher-register.tsx

@@ -222,6 +222,10 @@ export default defineComponent({
       }
     })
 
+    const onPreview = () => {
+      window.open(window.location.origin + '/#/preview-protocol', '_blank')
+    }
+
     return () => (
       <div class={styles.register}>
         <div class={styles.title}>
@@ -303,6 +307,7 @@ export default defineComponent({
                 }
               ]}
               name="idCardNo"
+              maxlength={18}
               placeholder="请输入身份证号码"
             ></Field>
 
@@ -348,8 +353,8 @@ export default defineComponent({
               v-model={state.forms.idcardFrontImg}
               readonly
               name="idcardFrontImg"
-              rules={[{ required: true, message: '请选择身份证照片正面', trigger: 'onChange' }]}
-              placeholder="请选择身份证照片正面"
+              rules={[{ required: true, message: '请上传身份证正面', trigger: 'onChange' }]}
+              placeholder="请上传身份证正面"
             >
               {{
                 input: () => (
@@ -367,8 +372,8 @@ export default defineComponent({
               v-model={state.forms.idcardBackImg}
               readonly
               name="idcardBackImg"
-              rules={[{ required: true, message: '请选择身份证照片反面', trigger: 'onChange' }]}
-              placeholder="请选择身份证照片反面"
+              rules={[{ required: true, message: '请上传身份证反面', trigger: 'onChange' }]}
+              placeholder="请上传身份证反面"
             >
               {{
                 input: () => (
@@ -483,7 +488,9 @@ export default defineComponent({
             >
               请认真阅读并勾选
             </span>
-            <span class={styles.c}>《乐团伴学老师注册协议》</span>
+            <span class={styles.c} onClick={onPreview}>
+              《乐团伴学老师注册协议》
+            </span>
           </div>
 
           <Button
@@ -541,7 +548,7 @@ export default defineComponent({
         </Popup>
         <Popup v-model:show={state.submitStatus} round style="width: 75%" closeOnClickOverlay>
           <div class={styles.stautsS}>
-            <img
+            {/* <img
               class={styles['icon-close']}
               src={iconClose}
               onClick={() => {
@@ -549,12 +556,13 @@ export default defineComponent({
                 window.location.href =
                   window.location.origin + '/orchestra-student/#/download?type=teacher'
               }}
-            />
+            /> */}
             <img src={topBanner1} class={styles['submit-img']} />
             <div class={styles['submit-container']}>
               <p class={styles['submit-title']}>恭喜您已成功登记为</p>
               <p class={styles['submit-o']}>
-                {state.name} <span>【伴学老师】</span>
+                {state.name} <br />
+                <span>【伴学老师】</span>
               </p>
               <p class={styles['submit-tips']}>请下载管乐团老师端APP进行授课</p>
               <Button

+ 56 - 2
src/school/companion-teacher/index.tsx

@@ -17,6 +17,7 @@ import {
   showFailToast,
   showLoadingToast,
   showSuccessToast,
+  showToast,
   Tag
 } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
@@ -163,7 +164,8 @@ export default defineComponent({
 
     const imgs = reactive({
       saveLoading: false,
-      image: null as any
+      image: null as any,
+      shareLoading: false
     })
     const onSaveImg = async () => {
       // 判断是否在保存中...
@@ -192,6 +194,58 @@ export default defineComponent({
           })
       }
     }
+    const onShare = () => {
+      if (imgs.shareLoading) {
+        return
+      }
+      imgs.shareLoading = true
+      if (imgs.image) {
+        openShare()
+      } else {
+        const container: any = document.getElementById(`preview-container`)
+        html2canvas(container, {
+          allowTaint: true,
+          useCORS: true,
+          backgroundColor: null
+        })
+          .then(async (canvas) => {
+            const url = canvas.toDataURL('image/png')
+            imgs.image = url
+            openShare()
+          })
+          .catch(() => {
+            closeToast()
+            imgs.shareLoading = false
+          })
+      }
+    }
+    const openShare = () => {
+      const image = imgs.image
+      setTimeout(() => {
+        imgs.shareLoading = false
+      }, 100)
+      if (image) {
+        postMessage(
+          {
+            api: 'shareTripartite',
+            content: {
+              title: '',
+              desc: '',
+              image,
+              video: '',
+              type: 'image',
+              // button: ['copy']
+              shareType: 'wechat'
+            }
+          },
+          (res: any) => {
+            if (res && res.content) {
+              showToast(res.content.message || (res.content.status ? '分享成功' : '分享失败'))
+            }
+          }
+        )
+      }
+    }
     const saveImg = async () => {
       showLoadingToast({ message: '图片生成中...', forbidClick: true })
       setTimeout(() => {
@@ -360,7 +414,7 @@ export default defineComponent({
                     text: () => <div class={styles.shareText}>保存图片</div>
                   }}
                 </GridItem>
-                <GridItem>
+                <GridItem onClick={onShare}>
                   {{
                     icon: () => <Image class={styles.shareImg} src={iconWechat} />,
                     text: () => <div class={styles.shareText}>微信</div>

+ 8 - 0
src/school/companion-teacher/unbind.module.less

@@ -25,11 +25,18 @@
     }
   }
 
+  .valueClass {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+  }
+
   .teacherName {
     font-size: 16px;
     font-weight: 500;
     color: #333333;
     line-height: 22px;
+    max-width: 60px;
   }
 
   .teacherContent {
@@ -57,6 +64,7 @@
     font-size: 14px;
     color: #777777;
     line-height: 20px;
+    max-width: 120px;
   }
 
   .nums {

+ 11 - 3
src/school/companion-teacher/unbind.tsx

@@ -104,13 +104,19 @@ export default defineComponent({
 
         <CellGroup inset class={styles.detailCellGroup}>
           {state.classList.map((item: any) => (
-            <Cell center class={styles.detailCell} isLink onClick={() => onSelectTeacher(item)}>
+            <Cell
+              center
+              class={styles.detailCell}
+              isLink
+              onClick={() => onSelectTeacher(item)}
+              valueClass={styles.valueClass}
+            >
               {{
                 title: () => (
                   <div class={styles.teacherContent}>
                     <div class={styles.classInfo}>
                       <p class={styles.className}>{item.name}</p>
-                      <p class={styles.musicName}>{item.orchestraName}</p>
+                      <p class={[styles.musicName, 'van-ellipsis']}>{item.orchestraName}</p>
                     </div>
                     <div class={styles.classNum}>
                       <p class={styles.nums}>
@@ -121,7 +127,9 @@ export default defineComponent({
                   </div>
                 ),
                 value: () => (
-                  <span class={styles.teacherName}>{item.sTeacher && item.sTeacher.nickname}</span>
+                  <div class={[styles.teacherName, 'van-ellipsis']}>
+                    {item.sTeacher && item.sTeacher.nickname}
+                  </div>
                 )
               }}
             </Cell>

+ 2 - 2
src/school/main.ts

@@ -30,8 +30,8 @@ postMessage(
     console.log(res, 'version')
   }
 )
-import Vconsole from 'vconsole'
-const vconsole = new Vconsole()
+// import Vconsole from 'vconsole'
+// const vconsole = new Vconsole()
 const paymentType = (window as any).paymentType // 浏览器设置
 if (browser().isTeacher || paymentType === 'TEACHER') {
   state.platformType = 'TEACHER'

+ 1 - 0
src/school/manage-teacher/index.module.less

@@ -21,6 +21,7 @@
     font-weight: 500;
     color: #333333;
     line-height: 22px;
+    max-width: 200px;
   }
   .phone {
     font-size: 14px;

+ 60 - 6
src/school/manage-teacher/index.tsx

@@ -14,7 +14,8 @@ import {
   Popup,
   showFailToast,
   showLoadingToast,
-  showSuccessToast
+  showSuccessToast,
+  showToast
 } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import styles from './index.module.less'
@@ -27,7 +28,7 @@ import { state } from '@/state'
 import OEmpty from '@/components/o-empty'
 import { manageTeacherType } from '@/constant'
 import html2canvas from 'html2canvas'
-import { promisefiyPostMessage } from '@/helpers/native-message'
+import { promisefiyPostMessage, postMessage } from '@/helpers/native-message'
 
 export default defineComponent({
   name: 'companion-teacher',
@@ -116,8 +117,61 @@ export default defineComponent({
 
     const imgs = reactive({
       saveLoading: false,
-      image: null as any
+      image: null as any,
+      shareLoading: false
     })
+    const onShare = () => {
+      if (imgs.shareLoading) {
+        return
+      }
+      imgs.shareLoading = true
+      if (imgs.image) {
+        openShare()
+      } else {
+        const container: any = document.getElementById(`preview-container`)
+        html2canvas(container, {
+          allowTaint: true,
+          useCORS: true,
+          backgroundColor: null
+        })
+          .then(async (canvas) => {
+            const url = canvas.toDataURL('image/png')
+            imgs.image = url
+            openShare()
+          })
+          .catch(() => {
+            closeToast()
+            imgs.shareLoading = false
+          })
+      }
+    }
+    const openShare = () => {
+      const image = imgs.image
+      setTimeout(() => {
+        imgs.shareLoading = false
+      }, 100)
+      if (image) {
+        postMessage(
+          {
+            api: 'shareTripartite',
+            content: {
+              title: '',
+              desc: '',
+              image,
+              video: '',
+              type: 'image',
+              // button: ['copy']
+              shareType: 'wechat'
+            }
+          },
+          (res: any) => {
+            if (res && res.content) {
+              showToast(res.content.message || (res.content.status ? '分享成功' : '分享失败'))
+            }
+          }
+        )
+      }
+    }
     const onSaveImg = async () => {
       // 判断是否在保存中...
       if (imgs.saveLoading) {
@@ -217,7 +271,7 @@ export default defineComponent({
                   ),
                   title: () => (
                     <div class={styles.content}>
-                      <p class={styles.name}>{item.nickname}</p>
+                      <p class={[styles.name, 'van-ellipsis']}>{item.nickname}</p>
                       <p class={styles.phone}>{item.phone}</p>
                     </div>
                   ),
@@ -274,7 +328,7 @@ export default defineComponent({
                     text: () => <div class={styles.shareText}>保存图片</div>
                   }}
                 </GridItem>
-                <GridItem>
+                <GridItem onClick={onShare}>
                   {{
                     icon: () => <Image class={styles.shareImg} src={iconWechat} />,
                     text: () => <div class={styles.shareText}>微信</div>
@@ -293,7 +347,7 @@ export default defineComponent({
           actions={
             [
               { name: '全部', id: 'ALL' },
-              { name: '注销', id: 'CANCEL' },
+              // { name: '注销', id: 'CANCEL' },
               { name: '冻结', id: 'LOCKED' },
               { name: '正常', id: 'ACTIVATION' }
             ] as any

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

@@ -20,6 +20,7 @@
     font-weight: 500;
     color: #333333;
     line-height: 22px;
+    max-width: 220px;
   }
   .phone {
     font-size: 14px;

+ 24 - 7
src/school/manage-teacher/manage-detail.tsx

@@ -30,11 +30,23 @@ export default defineComponent({
     // 冻结
     const onFreeze = async () => {
       try {
+        // {
+        //   detail.value.status === 'ACTIVATION' && '冻结账号'
+        // }
+        // {
+        //   detail.value.status === 'LOCKED' && '解冻账号'
+        // }
+
         const query = route.query
         await request.post('/api-school/schoolStaff/freeze/' + query.id)
         setTimeout(() => {
-          detail.value.status = 'LOCKED'
-          showToast('冻结成功')
+          if (detail.value.status === 'ACTIVATION') {
+            detail.value.status = 'LOCKED'
+            showToast('冻结成功')
+          } else {
+            detail.value.status = 'ACTIVATION'
+            showToast('解冻成功')
+          }
         }, 100)
       } catch {
         //
@@ -59,7 +71,7 @@ export default defineComponent({
               ),
               title: () => (
                 <div class={styles.content}>
-                  <p class={styles.name}>{detail.value.nickname}</p>
+                  <p class={[styles.name, 'van-ellipsis']}>{detail.value.nickname}</p>
                   <p class={styles.phone}>{detail.value.phone}</p>
                 </div>
               )
@@ -111,9 +123,10 @@ export default defineComponent({
             round
             color="#64A9FF"
             onClick={() => (state.status = true)}
-            disabled={detail.value.status === 'ACTIVATION' ? false : true}
+            // disabled={ ? false : true}
           >
-            冻结账号
+            {detail.value.status === 'ACTIVATION' && '冻结账号'}
+            {detail.value.status === 'LOCKED' && '解冻账号'}
           </Button>
         </div>
 
@@ -128,7 +141,11 @@ export default defineComponent({
 
         <Dialog
           v-model:show={state.status}
-          message="冻结后该管理老师将无法登录\n确认需要冻结吗?"
+          message={`${
+            detail.value.status === 'ACTIVATION'
+              ? '冻结后该管理老师将无法登录\n确认需要冻结吗?'
+              : '确认需要解冻吗?'
+          }`}
           messageAlign="left"
           showCancelButton
           onConfirm={onFreeze}
@@ -137,7 +154,7 @@ export default defineComponent({
             title: () => (
               <div class={styles.dialogTitle}>
                 <i></i>
-                冻结管理老师
+                {detail.value.status === 'ACTIVATION' ? '冻结' : '解冻'}管理老师
               </div>
             )
           }}

+ 11 - 4
src/school/manage-teacher/manage-teacher-register.tsx

@@ -151,6 +151,10 @@ export default defineComponent({
       }
     })
 
+    const onPreview = () => {
+      window.open(window.location.origin + '/#/preview-protocol', '_blank')
+    }
+
     return () => (
       <div class={styles.register}>
         <div class={styles.title}>
@@ -286,7 +290,9 @@ export default defineComponent({
             >
               请认真阅读并勾选
             </span>
-            <span class={styles.c}>《乐团伴学老师注册协议》</span>
+            <span class={styles.c} onClick={onPreview}>
+              《乐团伴学老师注册协议》
+            </span>
           </div>
 
           <Button
@@ -304,7 +310,7 @@ export default defineComponent({
 
         <Popup v-model:show={state.submitStatus} round style="width: 75%" closeOnClickOverlay>
           <div class={styles.stautsS}>
-            <img
+            {/* <img
               class={styles['icon-close']}
               src={iconClose}
               onClick={() => {
@@ -312,12 +318,13 @@ export default defineComponent({
                 window.location.href =
                   window.location.origin + '/orchestra-student/#/download?type=manage'
               }}
-            />
+            /> */}
             <img src={topBanner1} class={styles['submit-img']} />
             <div class={styles['submit-container']}>
               <p class={styles['submit-title']}>恭喜您已成功登记为</p>
               <p class={styles['submit-o']}>
-                {state.name} <span>【管理老师】</span>
+                {state.name} <br />
+                <span>【管理老师】</span>
               </p>
               <p class={styles['submit-tips']}>请下载管乐团管理端APP</p>
               <Button

+ 115 - 0
src/school/mass-message/component/class-list/index.module.less

@@ -0,0 +1,115 @@
+.searchBand {
+  display: inline-block;
+  font-size: 14px;
+  font-weight: 600;
+  color: #333333;
+}
+
+.gridContainer {
+  margin: 0 13px 12px;
+  // background: #ffffff;
+
+  .title {
+    font-size: 26px;
+    font-weight: bold;
+    color: #333;
+    i {
+      font-style: normal;
+      font-size: 12px;
+      color: #333333;
+    }
+  }
+  .red {
+    color: #f67146;
+  }
+  .name {
+    padding-top: 8px;
+    font-size: 12px;
+    color: #777777;
+  }
+}
+
+.gridClass {
+  .img {
+    width: 40px;
+    height: 40px;
+    margin-right: 12px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+  .teacherName {
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    font-weight: 600;
+    color: #333333;
+    line-height: 22px;
+    .name {
+      padding: 0;
+      max-width: 120px;
+    }
+    :global {
+      .van-tag {
+        margin-left: 6px;
+      }
+    }
+  }
+  .classCheckbox {
+    display: flex;
+    justify-content: flex-end;
+  }
+  .orchestraName {
+    padding-top: 3px;
+    font-size: 12px;
+    color: #777777;
+    line-height: 17px;
+    max-width: 220px;
+  }
+  .title {
+    font-size: 24px;
+  }
+
+  .className {
+    padding: 17px 15px 0;
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 22px;
+    .line {
+      display: inline-block;
+      width: 4px;
+      height: 12px;
+      background: #ff8057;
+      border-radius: 3px;
+      margin-right: 6px;
+    }
+  }
+
+  :global {
+    .van-grid-item {
+      &:after {
+        content: ' ';
+        position: absolute;
+        top: 50%;
+        right: 0;
+        margin-top: -10px;
+        width: 1px;
+        height: 20px;
+        background: #eaeaea;
+        border-radius: 1px;
+      }
+
+      &:last-child {
+        &::after {
+          display: none;
+        }
+      }
+    }
+  }
+}
+
+.classCellGroup {
+  margin-bottom: 12px;
+  border-radius: 10px;
+  overflow: hidden;
+}

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

@@ -0,0 +1,268 @@
+import OHeader from '@/components/o-header'
+import OSticky from '@/components/o-sticky'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Checkbox,
+  CheckboxGroup,
+  Grid,
+  GridItem,
+  Icon,
+  Image,
+  List,
+  Picker,
+  Popup,
+  Sticky,
+  Tag
+} from 'vant'
+import { defineComponent, onMounted, reactive, watch } from 'vue'
+import styles from './index.module.less'
+import iconTeacher from '@common/images/icon_teacher.png'
+import { state as baseState } from '@/state'
+import request from '@/helpers/request'
+import OEmpty from '@/components/o-empty'
+
+export default defineComponent({
+  name: 'practice-class',
+  props: {
+    height: {
+      type: [String, Number],
+      default: 'auto'
+    },
+    selectItem: {
+      type: Array,
+      default: []
+    }
+  },
+  emits: ['close', 'confirm', 'update:selectItem'],
+  setup(props, { emit }) {
+    const forms = reactive({
+      showPopover: false,
+      orchestraId: null as any,
+      orchestraName: null as any,
+      orchestraList: [] as any,
+      isClick: false,
+      list: [] as any,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false
+      },
+      params: {
+        type: null,
+        page: 1,
+        rows: 20
+      },
+      check: [] as any,
+      checkboxRefs: [] as any
+    })
+    // 获取乐团列表
+    const getOrchestras = async () => {
+      try {
+        const { data } = await request.post('/api-school/orchestra/page', {
+          data: {
+            page: 1,
+            rows: 100,
+            schoolId: baseState.user.data.school.id
+          }
+        })
+        const temps = data.rows || []
+        const s = [] as any
+        temps.forEach((item: any) => {
+          s.push({
+            text: item.name,
+            value: item.id
+          })
+        })
+        forms.orchestraList = [...s]
+
+        // 判断是否有乐团
+        if (s.length > 0) {
+          forms.orchestraId = s[0].value
+          forms.orchestraName = s[0].text
+        }
+      } catch {
+        //
+      }
+    }
+
+    // 获取班级
+    const getList = async () => {
+      // 查询没有设置指导老师的班级
+      try {
+        if (forms.isClick) return
+        forms.isClick = true
+        const { data } = await request.post('/api-school/classGroup/page', {
+          data: {
+            ...forms.params,
+            schoolId: baseState.user.data.school.id
+          }
+        })
+        forms.isClick = false
+        // 班级数据
+        forms.listState.loading = false
+        const result = data || {}
+        // 处理重复请求数据
+        if (forms.list.length > 0 && result.current === 1) {
+          return
+        }
+        forms.list = forms.list.concat(result.rows || [])
+        forms.listState.finished = result.current >= result.pages
+        forms.params.page = result.current + 1
+        forms.listState.dataShow = forms.list.length > 0
+      } catch {
+        forms.listState.dataShow = false
+        forms.listState.finished = true
+        forms.isClick = false
+      }
+    }
+
+    const onSelect = (type: string) => {
+      forms.checkboxRefs[type].toggle()
+
+      const list: any = []
+      forms.list.forEach((item: any) => {
+        if (forms.check.includes(item.id)) {
+          list.push({
+            id: item.id,
+            value: item.name,
+            avatar: ''
+          })
+        }
+      })
+      emit('update:selectItem', list)
+    }
+
+    watch(
+      () => props.selectItem,
+      () => {
+        initSelectItem()
+      },
+      { deep: true }
+    )
+
+    const initSelectItem = () => {
+      const selectItem = props.selectItem || []
+      const temp: any = []
+      selectItem.forEach((item: any) => {
+        temp.push(item.id)
+      })
+      forms.check = temp
+    }
+    onMounted(async () => {
+      await getOrchestras()
+      await getList()
+      initSelectItem()
+    })
+
+    return () => (
+      <div class={styles.practiceClass}>
+        <Sticky position="top" offsetTop={props.height}>
+          <div style={{ padding: '12px 13px', background: '#f6f6f6' }}>
+            <div class={styles.searchBand} onClick={() => (forms.showPopover = true)}>
+              {forms.orchestraName} <Icon name={forms.showPopover ? 'arrow-up' : 'arrow-down'} />
+            </div>
+          </div>
+        </Sticky>
+
+        {forms.listState.dataShow ? (
+          <List
+            v-model:loading={forms.listState.loading}
+            finished={forms.listState.finished}
+            finishedText=" "
+            class={[styles.liveList]}
+            onLoad={getList}
+            immediateCheck={false}
+          >
+            <CheckboxGroup
+              class={[styles.gridContainer, styles.gridClass]}
+              v-model={forms.check}
+              // onChange={(val: any) => {
+              //   console.log(val, '1212')
+              //   const list: any = []
+              //   forms.list.forEach((item: any) => {
+              //     if (val.includes(item.id)) {
+              //       list.push({
+              //         id: item.id,
+              //         value: item.name,
+              //         avatar: ''
+              //       })
+              //     }
+              //   })
+              //   emit('change', list)
+              // }}
+            >
+              {forms.list.map((item: any) => (
+                <CellGroup class={styles.classCellGroup} onClick={() => onSelect(item.id)}>
+                  <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}>
+                            <span class={['van-ellipsis', styles.name]}>{item.teacherName}</span>
+                            <Tag type="primary">{item.name}</Tag>
+                          </div>
+                          <div class={[styles.orchestraName, 'van-ellipsis']}>
+                            {item.orchestraName}
+                          </div>
+                        </div>
+                      ),
+                      value: () => (
+                        <Checkbox
+                          name={item.id}
+                          ref={(el: any) => (forms.checkboxRefs[item.id] = el)}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                          }}
+                        ></Checkbox>
+                      )
+                    }}
+                  </Cell>
+                  <Grid border={false} columnNum={3}>
+                    <GridItem>
+                      <p class={styles.title}>{item.preStudentNum}</p>
+                      <p class={styles.name}>学生人数</p>
+                    </GridItem>
+                    <GridItem>
+                      <p class={[styles.title]}>
+                        {item.courseScheduleNum - item.completeCourseScheduleNum}
+                      </p>
+                      <p class={styles.name}>剩余课时</p>
+                    </GridItem>
+                    <GridItem>
+                      <p class={styles.title}>{item.courseScheduleNum}</p>
+                      <p class={styles.name}>总课时</p>
+                    </GridItem>
+                  </Grid>
+                </CellGroup>
+              ))}
+            </CheckboxGroup>
+          </List>
+        ) : (
+          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无班级" />
+        )}
+
+        <Popup v-model:show={forms.showPopover} position="bottom" round>
+          <Picker
+            columns={forms.orchestraList}
+            onCancel={() => (forms.showPopover = false)}
+            onConfirm={(val: any) => {
+              forms.orchestraId = val.selectedOptions[0].value
+              forms.orchestraName = val.selectedOptions[0].text
+              forms.showPopover = false
+
+              forms.params.page = 1
+              forms.list = []
+              forms.listState.dataShow = true // 判断是否有数据
+              forms.listState.loading = false
+              forms.listState.finished = false
+              getList()
+            }}
+          />
+        </Popup>
+      </div>
+    )
+  }
+})

+ 46 - 0
src/school/mass-message/component/manage-list/index.module.less

@@ -0,0 +1,46 @@
+.cellTeacher {
+  .img {
+    width: 48px;
+    height: 48px;
+    overflow: hidden;
+    border-radius: 50%;
+    margin-right: 12px;
+    flex-shrink: 0;
+  }
+
+  .name {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 22px;
+    max-width: 120px;
+  }
+
+  .class {
+    font-size: 12px;
+    color: #777777;
+    line-height: 17px;
+  }
+
+  :global {
+    .van-tag {
+      margin-top: 3px;
+      margin-right: 8px;
+    }
+  }
+
+  .checkboxValue {
+    display: flex;
+    justify-content: flex-end;
+  }
+}
+
+.subjectContainer {
+  display: flex;
+  & > span {
+    flex-shrink: 0;
+  }
+  .tagSubject {
+    margin-right: 10px;
+  }
+}

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

@@ -0,0 +1,200 @@
+import OHeader from '@/components/o-header'
+import OSticky from '@/components/o-sticky'
+import { state } from '@/state'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Field,
+  List,
+  Image,
+  Tag,
+  Sticky,
+  Checkbox,
+  CheckboxGroup
+} from 'vant'
+import { defineComponent, onMounted, PropType, reactive, watch } from 'vue'
+import styles from './index.module.less'
+import iconTeacher from '@common/images/icon_teacher.png'
+import OEmpty from '@/components/o-empty'
+import request from '@/helpers/request'
+import OSearch from '@/components/o-search'
+
+export default defineComponent({
+  name: 'manage-list',
+  props: {
+    height: {
+      type: [String, Number],
+      default: 'auto'
+    },
+    removeTeacherId: {
+      type: String,
+      default: ''
+    },
+    subjectIdList: {
+      type: Array,
+      default: () => []
+    },
+    selectItem: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'select', 'update:selectItem'],
+  setup(props, { slots, attrs, emit }) {
+    const forms = reactive({
+      teacherStatus: false,
+      isLoad: false,
+      list: [] as any,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false
+      },
+      params: {
+        keyword: null,
+        subjectIdList: props.subjectIdList,
+        page: 1,
+        rows: 20
+      },
+      check: (props.selectItem || []) as any,
+      checkboxRefs: [] as any
+    })
+
+    const getList = async () => {
+      try {
+        if (forms.isLoad) return
+        forms.isLoad = true
+        const res = await request.post('/api-school/schoolStaff/page', {
+          data: {
+            ...forms.params,
+            schoolId: state.user.data.school.id,
+            status: 'ACTIVATION'
+          }
+        })
+        forms.listState.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (forms.list.length > 0 && result.current === 1) {
+          return
+        }
+        const rows = result.rows || []
+        rows.forEach((item: any) => {
+          item.subjectNames = item.subjectName ? item.subjectName.split(',') : []
+        })
+        forms.list = forms.list.concat(rows)
+        forms.listState.finished = result.current >= result.pages
+        forms.params.page = result.current + 1
+        forms.listState.dataShow = forms.list.length > 0
+
+        forms.isLoad = false
+      } catch {
+        forms.listState.dataShow = false
+        forms.listState.finished = true
+        forms.isLoad = false
+      }
+    }
+
+    // 搜索
+    const onSearch = () => {
+      forms.params.page = 1
+      forms.list = []
+      forms.listState.dataShow = true // 判断是否有数据
+      forms.listState.loading = false
+      forms.listState.finished = false
+      getList()
+    }
+
+    const onSelect = (type: string) => {
+      forms.checkboxRefs[type].toggle()
+
+      const list: any = []
+      forms.list.forEach((item: any) => {
+        if (forms.check.includes(item.id)) {
+          list.push({
+            id: item.id,
+            value: item.nickname,
+            avatar: item.avatar
+          })
+        }
+      })
+      emit('update:selectItem', list)
+    }
+
+    watch(
+      () => props.selectItem,
+      () => {
+        initSelectItem()
+      },
+      { deep: true }
+    )
+
+    const initSelectItem = () => {
+      const selectItem = props.selectItem || []
+      const temp: any = []
+      selectItem.forEach((item: any) => {
+        temp.push(item.id)
+      })
+      forms.check = temp
+    }
+
+    onMounted(() => {
+      getList()
+      initSelectItem()
+    })
+
+    return () => (
+      <div>
+        <Sticky position="top" offsetTop={props.height}>
+          <OSearch
+            inputBackground="white"
+            background="#F8F8F8"
+            placeholder="老师名称/手机号"
+            onSearch={(val: any) => {
+              forms.params.keyword = val
+              onSearch()
+            }}
+          />
+        </Sticky>
+
+        {forms.listState.dataShow ? (
+          <List
+            v-model:loading={forms.listState.loading}
+            finished={forms.listState.finished}
+            finishedText=" "
+            onLoad={getList}
+            immediateCheck={false}
+          >
+            <CheckboxGroup class={[styles.gridContainer, styles.gridClass]} v-model={forms.check}>
+              {forms.list.map((item: any) => (
+                <CellGroup inset style={{ marginBottom: '12px' }} onClick={() => onSelect(item.id)}>
+                  <Cell center class={styles.cellTeacher} valueClass={styles.checkboxValue}>
+                    {{
+                      icon: () => <Image class={styles.img} src={item.avatar || iconTeacher} />,
+                      title: () => (
+                        <div class={styles.content}>
+                          <p class={[styles.name, 'van-ellipsis']}>{item.nickname}</p>
+                        </div>
+                      ),
+                      value: () => (
+                        <Checkbox
+                          name={item.id}
+                          ref={(el: any) => (forms.checkboxRefs[item.id] = el)}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                          }}
+                        ></Checkbox>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))}
+            </CheckboxGroup>
+          </List>
+        ) : (
+          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+        )}
+      </div>
+    )
+  }
+})

+ 46 - 0
src/school/mass-message/component/student-list/index.module.less

@@ -0,0 +1,46 @@
+.cellTeacher {
+  .img {
+    width: 48px;
+    height: 48px;
+    overflow: hidden;
+    border-radius: 50%;
+    margin-right: 12px;
+    flex-shrink: 0;
+  }
+
+  .name {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 22px;
+    max-width: 120px;
+  }
+
+  .class {
+    font-size: 12px;
+    color: #777777;
+    line-height: 17px;
+  }
+
+  :global {
+    .van-tag {
+      margin-top: 3px;
+      margin-right: 8px;
+    }
+  }
+
+  .checkboxValue {
+    display: flex;
+    justify-content: flex-end;
+  }
+}
+
+.subjectContainer {
+  display: flex;
+  & > span {
+    flex-shrink: 0;
+  }
+  .tagSubject {
+    margin-right: 10px;
+  }
+}

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

@@ -0,0 +1,196 @@
+import { state } from '@/state'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Field,
+  List,
+  Image,
+  Tag,
+  Sticky,
+  Checkbox,
+  CheckboxGroup
+} from 'vant'
+import { defineComponent, onMounted, PropType, reactive, watch } from 'vue'
+import styles from './index.module.less'
+import iconTeacher from '@common/images/icon_teacher.png'
+import OEmpty from '@/components/o-empty'
+import request from '@/helpers/request'
+import OSearch from '@/components/o-search'
+
+export default defineComponent({
+  name: 'teacher-list',
+  props: {
+    height: {
+      type: [String, Number],
+      default: 'auto'
+    },
+    selectItem: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'select', 'update:selectItem'],
+  setup(props, { slots, attrs, emit }) {
+    const forms = reactive({
+      teacherStatus: false,
+      isLoad: false,
+      list: [] as any,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false
+      },
+      params: {
+        keyword: null,
+        page: 1,
+        rows: 20
+      },
+      check: (props.selectItem || []) as any,
+      checkboxRefs: [] as any
+    })
+
+    const getList = async () => {
+      try {
+        if (forms.isLoad) return
+        forms.isLoad = true
+        const res = await request.post('/api-school/student/page', {
+          data: {
+            ...forms.params,
+            schoolId: state.user.data.school.id
+          }
+        })
+        forms.listState.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (forms.list.length > 0 && result.current === 1) {
+          return
+        }
+        const rows = result.rows || []
+        rows.forEach((item: any) => {
+          item.subjectNames = item.subjectNames ? item.subjectNames.split(',') : []
+        })
+        forms.list = forms.list.concat(rows)
+        forms.listState.finished = result.current >= result.pages
+        forms.params.page = result.current + 1
+        forms.listState.dataShow = forms.list.length > 0
+
+        forms.isLoad = false
+      } catch {
+        forms.listState.dataShow = false
+        forms.listState.finished = true
+        forms.isLoad = false
+      }
+    }
+
+    // 搜索
+    const onSearch = () => {
+      forms.params.page = 1
+      forms.list = []
+      forms.listState.dataShow = true // 判断是否有数据
+      forms.listState.loading = false
+      forms.listState.finished = false
+      getList()
+    }
+
+    const onSelect = (type: string) => {
+      forms.checkboxRefs[type].toggle()
+
+      const list: any = []
+      forms.list.forEach((item: any) => {
+        if (forms.check.includes(item.id)) {
+          list.push({
+            id: item.id,
+            value: item.nickname,
+            avatar: item.avatar
+          })
+        }
+      })
+      emit('update:selectItem', list)
+    }
+
+    watch(
+      () => props.selectItem,
+      () => {
+        initSelectItem()
+      },
+      { deep: true }
+    )
+
+    const initSelectItem = () => {
+      const selectItem = props.selectItem || []
+      const temp: any = []
+      selectItem.forEach((item: any) => {
+        temp.push(item.id)
+      })
+      forms.check = temp
+    }
+
+    onMounted(() => {
+      getList()
+      initSelectItem()
+    })
+
+    return () => (
+      <div>
+        <Sticky position="top" offsetTop={props.height}>
+          <OSearch
+            inputBackground="white"
+            background="#F8F8F8"
+            placeholder="老师名称/手机号"
+            onSearch={(val: any) => {
+              forms.params.keyword = val
+              onSearch()
+            }}
+          />
+        </Sticky>
+
+        {forms.listState.dataShow ? (
+          <List
+            v-model:loading={forms.listState.loading}
+            finished={forms.listState.finished}
+            finishedText=" "
+            onLoad={getList}
+            immediateCheck={false}
+          >
+            <CheckboxGroup class={[styles.gridContainer, styles.gridClass]} v-model={forms.check}>
+              {forms.list.map((item: any) => (
+                <CellGroup inset style={{ marginBottom: '12px' }} onClick={() => onSelect(item.id)}>
+                  <Cell center class={styles.cellTeacher} valueClass={styles.checkboxValue}>
+                    {{
+                      icon: () => <Image class={styles.img} src={item.avatar || iconTeacher} />,
+                      title: () => (
+                        <div class={styles.content}>
+                          <p class={[styles.name, 'van-ellipsis']}>{item.nickname}</p>
+                          <p class={styles.class}>
+                            {item.subjectNames &&
+                              item.subjectNames.map((subject: any) => (
+                                <Tag type="primary" class={styles.tagSubject}>
+                                  {subject}
+                                </Tag>
+                              ))}
+                          </p>
+                        </div>
+                      ),
+                      value: () => (
+                        <Checkbox
+                          name={item.id}
+                          ref={(el: any) => (forms.checkboxRefs[item.id] = el)}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                          }}
+                        ></Checkbox>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))}
+            </CheckboxGroup>
+          </List>
+        ) : (
+          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+        )}
+      </div>
+    )
+  }
+})

+ 46 - 0
src/school/mass-message/component/teacher-list/teacher-list.module.less

@@ -0,0 +1,46 @@
+.cellTeacher {
+  .img {
+    width: 48px;
+    height: 48px;
+    overflow: hidden;
+    border-radius: 50%;
+    margin-right: 12px;
+    flex-shrink: 0;
+  }
+
+  .name {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 22px;
+    max-width: 120px;
+  }
+
+  .class {
+    font-size: 12px;
+    color: #777777;
+    line-height: 17px;
+  }
+
+  :global {
+    .van-tag {
+      margin-top: 3px;
+      margin-right: 8px;
+    }
+  }
+
+  .checkboxValue {
+    display: flex;
+    justify-content: flex-end;
+  }
+}
+
+.subjectContainer {
+  display: flex;
+  & > span {
+    flex-shrink: 0;
+  }
+  .tagSubject {
+    margin-right: 10px;
+  }
+}

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

@@ -0,0 +1,218 @@
+import OHeader from '@/components/o-header'
+import OSticky from '@/components/o-sticky'
+import { state } from '@/state'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Field,
+  List,
+  Image,
+  Tag,
+  Sticky,
+  Checkbox,
+  CheckboxGroup
+} from 'vant'
+import { defineComponent, onMounted, PropType, reactive, watch } from 'vue'
+import styles from './teacher-list.module.less'
+import iconTeacher from '@common/images/icon_teacher.png'
+import OEmpty from '@/components/o-empty'
+import request from '@/helpers/request'
+import OSearch from '@/components/o-search'
+
+export default defineComponent({
+  name: 'teacher-list',
+  props: {
+    height: {
+      type: [String, Number],
+      default: 'auto'
+    },
+    removeTeacherId: {
+      type: String,
+      default: ''
+    },
+    subjectIdList: {
+      type: Array,
+      default: () => []
+    },
+    selectItem: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'select', 'update:selectItem'],
+  setup(props, { slots, attrs, emit }) {
+    const forms = reactive({
+      teacherStatus: false,
+      isLoad: false,
+      list: [] as any,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false
+      },
+      params: {
+        keyword: null,
+        subjectIdList: props.subjectIdList,
+        page: 1,
+        rows: 20
+      },
+      check: (props.selectItem || []) as any,
+      checkboxRefs: [] as any
+    })
+
+    const getList = async () => {
+      try {
+        if (forms.isLoad) return
+        forms.isLoad = true
+        const res = await request.post('/api-school/teacher/page', {
+          data: {
+            ...forms.params,
+            schoolId: state.user.data.school.id,
+            removeTeacherId: props.removeTeacherId, // 移除的老师id
+            delFlag: false // 绑定解绑 false:绑定 true:解绑
+          }
+        })
+        forms.listState.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (forms.list.length > 0 && result.current === 1) {
+          return
+        }
+        const rows = result.rows || []
+        rows.forEach((item: any) => {
+          item.subjectNames = item.subjectName ? item.subjectName.split(',') : []
+        })
+        forms.list = forms.list.concat(rows)
+        forms.listState.finished = result.current >= result.pages
+        forms.params.page = result.current + 1
+        forms.listState.dataShow = forms.list.length > 0
+
+        forms.isLoad = false
+      } catch {
+        forms.listState.dataShow = false
+        forms.listState.finished = true
+        forms.isLoad = false
+      }
+    }
+
+    // 搜索
+    const onSearch = () => {
+      forms.params.page = 1
+      forms.list = []
+      forms.listState.dataShow = true // 判断是否有数据
+      forms.listState.loading = false
+      forms.listState.finished = false
+      getList()
+    }
+
+    const onSelect = (type: string) => {
+      forms.checkboxRefs[type].toggle()
+
+      const list: any = []
+      forms.list.forEach((item: any) => {
+        if (forms.check.includes(item.id)) {
+          list.push({
+            id: item.id,
+            value: item.nickname,
+            avatar: item.avatar
+          })
+        }
+      })
+      emit('update:selectItem', list)
+    }
+
+    watch(
+      () => props.selectItem,
+      () => {
+        initSelectItem()
+      },
+      { deep: true }
+    )
+
+    const initSelectItem = () => {
+      const selectItem = props.selectItem || []
+      const temp: any = []
+      selectItem.forEach((item: any) => {
+        temp.push(item.id)
+      })
+      forms.check = temp
+    }
+
+    onMounted(() => {
+      getList()
+      initSelectItem()
+    })
+
+    return () => (
+      <div>
+        <Sticky position="top" offsetTop={props.height}>
+          <OSearch
+            inputBackground="white"
+            background="#F8F8F8"
+            placeholder="老师名称/手机号"
+            onSearch={(val: any) => {
+              forms.params.keyword = val
+              onSearch()
+            }}
+          />
+        </Sticky>
+
+        {forms.listState.dataShow ? (
+          <List
+            v-model:loading={forms.listState.loading}
+            finished={forms.listState.finished}
+            finishedText=" "
+            onLoad={getList}
+            immediateCheck={false}
+          >
+            <CheckboxGroup class={[styles.gridContainer, styles.gridClass]} v-model={forms.check}>
+              {forms.list.map((item: any) => (
+                <CellGroup inset style={{ marginBottom: '12px' }} onClick={() => onSelect(item.id)}>
+                  <Cell center class={styles.cellTeacher} valueClass={styles.checkboxValue}>
+                    {{
+                      icon: () => <Image class={styles.img} src={item.avatar || iconTeacher} />,
+                      title: () => (
+                        <div class={styles.content}>
+                          <p class={[styles.name, 'van-ellipsis']}>{item.nickname}</p>
+                        </div>
+                      ),
+                      value: () => (
+                        <Checkbox
+                          name={item.id}
+                          ref={(el: any) => (forms.checkboxRefs[item.id] = el)}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                          }}
+                        ></Checkbox>
+                      )
+                    }}
+                  </Cell>
+                  <Cell>
+                    {{
+                      title: () => (
+                        <div class={styles.subjectContainer}>
+                          <span>声部:</span>
+                          <div>
+                            {item.subjectNames &&
+                              item.subjectNames.map((subject: any) => (
+                                <Tag type="primary" class={styles.tagSubject}>
+                                  {subject}
+                                </Tag>
+                              ))}
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))}
+            </CheckboxGroup>
+          </List>
+        ) : (
+          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无老师" />
+        )}
+      </div>
+    )
+  }
+})

+ 548 - 15
src/school/mass-message/create-message.tsx

@@ -1,41 +1,574 @@
-import OHeader from '@/components/o-header'
-import { Cell, CellGroup, Field, Uploader } from 'vant'
-import { defineComponent, reactive } from 'vue'
+import OPopup from '@/components/o-popup'
+import { sendType } from '@/constant'
+import request from '@/helpers/request'
+import { getOssUploadUrl } from '@/state'
+import dayjs from 'dayjs'
+import umiRequest from 'umi-request'
+import {
+  ActionSheet,
+  Button,
+  Cell,
+  CellGroup,
+  closeToast,
+  DatePicker,
+  Field,
+  Icon,
+  Image,
+  PickerGroup,
+  Popup,
+  showLoadingToast,
+  showToast,
+  TimePicker,
+  Uploader
+} from 'vant'
+import { computed, defineComponent, getCurrentInstance, onMounted, reactive, watch } from 'vue'
 import styles from './index.module.less'
+import SelectSned from './select-sned'
+import iconStudent from '@common/images/icon_student.png'
+import iconTeacher from '@common/images/icon_teacher.png'
+import ODialog from '@/components/o-dialog'
+import OSticky from '@/components/o-sticky'
+import { useRoute, useRouter } from 'vue-router'
 
 export default defineComponent({
   name: 'create-message',
   setup() {
+    const router = useRouter()
+    const route = useRoute()
     const forms = reactive({
-      type: null,
-      content: null
+      id: route.query.id,
+      type: 'ADD',
+      bucket: 'daya',
+      sendStatus: false,
+      sendType: 'IMMEDIATELY' as any,
+      textMessage: null,
+      sendTime: null as any,
+      sendTimeStatus: false,
+      maxDate: dayjs(new Date()).add(60, 'day').toDate(),
+      currentDate: [],
+      currentTime: [dayjs().format('HH'), dayjs().format('mm')],
+      attachments: [] as any, //群发消息附件
+      receives: [] as any, // 群发消息对象
+      selectStatus: false,
+      selectList: {} as any, // 选中发送的信息
+      delSelectItem: {} as any,
+      delStatus: false,
+      sureLoading: false,
+      updateLoading: false,
+      closeLoading: false
     })
+
+    const beforeRead = (file: any) => {
+      // console.log(file, 'beforeRead')
+      const isLt2M = file.size / 1024 / 1024 < 5
+      if (!isLt2M) {
+        showToast(`上传文件大小不能超过 5MB`)
+        return false
+      }
+      return true
+    }
+    const beforeDelete = (file: any, detail: { index: any }) => {
+      // this.dataModel.splice(detail.index, 1)
+      return true
+    }
+    const afterRead = async (file: any, detail: any) => {
+      try {
+        file.status = 'uploading'
+        file.message = '上传中...'
+        await uploadFile(file)
+      } catch (error) {
+        //
+        closeToast()
+      }
+    }
+
+    const uploadFile = async (files: any) => {
+      // 上传文件
+      try {
+        console.log(files, 'files')
+        const file = files.file
+        // 获取签名
+        const signUrl = '/api-school/open/getUploadSign'
+        const tempName = file.name || ''
+        const fileName = tempName && tempName.replace(/ /gi, '_')
+        const key = new Date().getTime() + fileName
+        showLoadingToast({
+          message: '加载中...',
+          forbidClick: true,
+          loadingType: 'spinner',
+          duration: 0
+        })
+        const res = await request.post(signUrl, {
+          hideLoading: true,
+          data: {
+            filename: fileName,
+            bucketName: forms.bucket,
+            postData: {
+              filename: fileName,
+              acl: 'public-read',
+              key: key,
+              unknowValueField: []
+            }
+          }
+        })
+        // setTimeout(() => {
+
+        // }, 100)
+        const obj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: key,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+        const formData = new FormData()
+        for (const key in obj) {
+          formData.append(key, obj[key])
+        }
+        formData.append('file', file, fileName)
+        await umiRequest(getOssUploadUrl(forms.bucket), {
+          method: 'POST',
+          data: formData
+        })
+        // console.log(getOssUploadUrl(state.bucket) + key)
+        const uploadUrl = getOssUploadUrl(forms.bucket) + key
+        closeToast()
+
+        // state.fileList.push({ url: uploadUrl })
+        files.url = uploadUrl
+        files.status = 'done'
+      } catch (error) {
+        files.status = 'failed'
+        closeToast()
+        console.log(error, 'uploadFile')
+      }
+    }
+
+    const onSubmit = async () => {
+      try {
+        if (!forms.sendType) {
+          showToast('请选择发送方式')
+          return
+        }
+
+        if (!forms.textMessage) {
+          showToast('请输入发送内容')
+          return
+        }
+
+        if (forms.receives.length <= 0) {
+          showToast('请选择发送对象')
+          return
+        }
+        const tempAttachments: any = []
+        forms.attachments.forEach((item: any) => {
+          tempAttachments.push({
+            imgUrl: item.url,
+            imgMessage: item.url
+          })
+        })
+
+        const tempReceives: any = []
+        forms.receives.forEach((item: any) => {
+          tempReceives.push({
+            receiveType: item.receiveType,
+            receiveId: item.receiveId
+          })
+        })
+
+        const params: any = {
+          sendType: forms.sendType,
+          textMessage: forms.textMessage,
+          attachments: tempAttachments,
+          receives: tempReceives,
+          sendTime: forms.sendTime
+        }
+
+        console.log(params, 'params')
+        if (forms.id) {
+          forms.updateLoading = true
+        } else {
+          forms.sureLoading = true
+        }
+
+        if (forms.id) {
+          params.id = forms.id
+          await request.post('/api-school/imMessageBatchSending/update', {
+            data: params
+          })
+        } else {
+          await request.post('/api-school/imMessageBatchSending/save', {
+            data: params
+          })
+        }
+
+        setTimeout(() => {
+          showToast(forms.id ? '修改成功' : '添加成功')
+        }, 100)
+        setTimeout(() => {
+          router.replace('/mass-message')
+          forms.sureLoading = false
+          forms.updateLoading = false
+        }, 1100)
+      } catch {
+        //
+        forms.sureLoading = false
+        forms.updateLoading = false
+      }
+    }
+
+    const getDetails = async () => {
+      try {
+        if (!forms.id) return
+        const { data } = await request.get('/api-school/imMessageBatchSending/detail/' + forms.id)
+        forms.sendType = data.sendType
+        forms.textMessage = data.textMessage
+        const receives = data.receives || []
+        const tempList: any = {
+          class: [] as any,
+          teacher: [] as any,
+          student: [] as any,
+          manage: [] as any
+        }
+        receives.forEach((item: any) => {
+          const temp = {
+            receiveType: item.receiveType,
+            receiveId: item.receiveId,
+            receiveName: item.receiveName,
+            avatar: item.avatar
+          }
+          forms.receives.push(temp)
+          const temp2 = {
+            id: item.receiveId,
+            value: item.receiveName,
+            avatar: item.avatar
+          }
+          if (item.receiveType === 'CLASS') {
+            tempList.class.push(temp2)
+          } else if (item.receiveType === 'STUDENT') {
+            tempList.student.push(temp2)
+          } else if (item.receiveType === 'TEACHER') {
+            tempList.teacher.push(temp2)
+          } else if (item.receiveType === 'SCHOOL') {
+            tempList.school.push(temp2)
+          }
+        })
+        forms.selectList = tempList
+        const attachments = data.attachments || []
+        const tempAtt: any = []
+        attachments.forEach((item: any) => {
+          tempAtt.push({
+            url: item.imgUrl || item.imgMessage
+          })
+        })
+        forms.attachments = tempAtt
+
+        forms.sendTime = data.sendTime
+        forms.type = data.sendStatus
+      } catch {
+        //
+      }
+    }
+
+    // 判断是否是查看
+    const formDisabled = computed(() => forms.type === 'SEND')
+
+    const onClose = async () => {
+      try {
+        forms.closeLoading = true
+        await request.post('/api-school/imMessageBatchSending/remove', {
+          requestType: 'form',
+          data: {
+            id: forms.id
+          }
+        })
+        setTimeout(() => {
+          showToast('撤销成功')
+        }, 100)
+        setTimeout(() => {
+          router.replace('/mass-message')
+          forms.closeLoading = false
+        }, 1100)
+      } catch {
+        //
+        forms.closeLoading = false
+      }
+    }
+
+    onMounted(() => {
+      getDetails()
+    })
+
     return () => (
       <div class={styles['create-message']}>
-        <OHeader />
-
-        <CellGroup inset>
-          <Cell title="发送方式" value={''} isLink />
-          <Cell title="发送时间" value={''} isLink />
+        {/* <OHeader /> */}
+        <CellGroup inset class={styles.cellGroup}>
+          <Field
+            inputAlign="right"
+            label="发送方式"
+            modelValue={sendType[forms.sendType]}
+            placeholder="请选择发送方式"
+            onClick={() => {
+              if (formDisabled.value) return
+              forms.sendStatus = true
+            }}
+            readonly
+            isLink={!formDisabled.value}
+          />
+          {/* 定时发送才会有时间 */}
+          {forms.sendType === 'SCHEDULED' && (
+            <Field
+              inputAlign="right"
+              label="发送时间"
+              modelValue={forms.sendTime}
+              placeholder="请选择发送时间"
+              onClick={() => {
+                if (formDisabled.value) return
+                forms.sendTimeStatus = true
+              }}
+              readonly
+              isLink
+            />
+          )}
           <Cell title="发送内容">
             {{
               label: () => (
                 <Field
-                  style={{ padding: '0' }}
+                  style={{ padding: '0', marginTop: '12px' }}
                   placeholder="请输入发送内容"
-                  v-model={forms.content}
+                  v-model={forms.textMessage}
                   type="textarea"
                   rows={3}
+                  showWordLimit
+                  maxlength={400}
+                  readonly={formDisabled.value}
                 />
               )
             }}
           </Cell>
-          <Cell title="上传辅件">
-            <Uploader />
+          <Cell title="上传附件">
+            {{
+              label: () => (
+                <Uploader
+                  style={{ marginTop: '12px' }}
+                  v-model={forms.attachments}
+                  afterRead={afterRead}
+                  beforeRead={beforeRead}
+                  beforeDelete={beforeDelete}
+                  accept="image/*"
+                  maxCount={9}
+                  disabled={formDisabled.value}
+                />
+              )
+            }}
           </Cell>
 
-          <Cell title="发送对象"></Cell>
+          <Field
+            label="发送对象"
+            readonly
+            inputAlign="right"
+            placeholder={formDisabled.value ? '' : '请选择发送对象'}
+            isLink={!formDisabled.value}
+            border={false}
+            onClick={() => {
+              if (formDisabled.value) return
+              forms.selectStatus = true
+            }}
+          />
+          {forms.receives.map((item: any) => {
+            let img: any = iconStudent
+            if (item.receiveType === 'CLASS') {
+              img = ''
+            } else if (item.receiveType === 'STUDENT') {
+              img = iconStudent
+            } else if (item.receiveType === 'TEACHER' || item.receiveType === 'SCHOOL') {
+              img = iconTeacher
+            }
+            return (
+              <Cell class={styles.receives} title={item.receiveName} center border={false}>
+                {{
+                  icon: () => <Image class={styles.img} src={item.avatar || img} />,
+                  extra: () =>
+                    !formDisabled.value && (
+                      <Icon
+                        name="clear"
+                        color="#d7d7d7"
+                        size={20}
+                        onClick={() => {
+                          forms.delSelectItem = item
+                          forms.delStatus = true
+                        }}
+                      />
+                    )
+                }}
+              </Cell>
+            )
+          })}
         </CellGroup>
+
+        <OSticky position="bottom">
+          {forms.type === 'ADD' && (
+            <div class={'btnGroup'}>
+              <Button
+                round
+                block
+                type="primary"
+                size="large"
+                onClick={onSubmit}
+                disabled={forms.sureLoading}
+              >
+                确认发送
+              </Button>
+            </div>
+          )}
+
+          {forms.type === 'WAIT' && (
+            <div class={['btnGroup', 'btnMore']}>
+              <Button
+                round
+                type="primary"
+                size="large"
+                onClick={onSubmit}
+                disabled={forms.updateLoading}
+              >
+                修改
+              </Button>
+              <Button
+                round
+                color="#64A9FF"
+                size="large"
+                onClick={onClose}
+                disabled={forms.closeLoading}
+              >
+                撤销
+              </Button>
+            </div>
+          )}
+        </OSticky>
+
+        <ActionSheet
+          v-model:show={forms.sendStatus}
+          cancelText="取消"
+          actions={
+            [
+              { name: '即时发送', value: 'IMMEDIATELY' },
+              { name: '定时发送', value: 'SCHEDULED' }
+            ] as any
+          }
+          onSelect={(val: any) => {
+            console.log(val)
+            forms.sendType = val.value
+            forms.sendStatus = false
+          }}
+        />
+
+        <Popup v-model:show={forms.sendTimeStatus} position="bottom" round>
+          <PickerGroup
+            title="发送时间"
+            tabs={['选择日期', '选择时间']}
+            onCancel={() => (forms.sendTimeStatus = false)}
+            onConfirm={(val: any) => {
+              const first = val[0].selectedValues.join('-')
+              const second = val[1].selectedValues.join(':')
+              forms.sendTime = dayjs(first + ' ' + second).format('YYYY-MM-DD HH:mm:ss')
+              forms.sendTimeStatus = false
+            }}
+          >
+            <DatePicker minDate={new Date()} maxDate={forms.maxDate} v-model={forms.currentDate} />
+            <TimePicker v-model={forms.currentTime} />
+          </PickerGroup>
+        </Popup>
+
+        <OPopup v-model:modelValue={forms.selectStatus}>
+          <SelectSned
+            v-model:selectList={forms.selectList}
+            onClose={() => (forms.selectStatus = false)}
+            onConfirm={(val: any) => {
+              const classList = val.class || []
+              const studentList = val.student || []
+              const teacherList = val.teacher || []
+              const schoolList = val.school || []
+
+              const tempList: any = []
+              classList.forEach((item: any) => {
+                tempList.push({
+                  receiveType: 'CLASS',
+                  receiveId: item.id,
+                  receiveName: item.value,
+                  avatar: item.avatar
+                })
+              })
+              studentList.forEach((item: any) => {
+                tempList.push({
+                  receiveType: 'STUDENT',
+                  receiveId: item.id,
+                  receiveName: item.value,
+                  avatar: item.avatar
+                })
+              })
+              teacherList.forEach((item: any) => {
+                tempList.push({
+                  receiveType: 'TEACHER',
+                  receiveId: item.id,
+                  receiveName: item.value,
+                  avatar: item.avatar
+                })
+              })
+              schoolList.forEach((item: any) => {
+                tempList.push({
+                  receiveType: 'SCHOOL',
+                  receiveId: item.id,
+                  receiveName: item.value,
+                  avatar: item.avatar
+                })
+              })
+
+              forms.receives = tempList
+            }}
+          />
+        </OPopup>
+
+        <ODialog
+          v-model:show={forms.delStatus}
+          showCancelButton
+          message="您是否删除该数据"
+          onConfirm={() => {
+            const selectList = forms.selectList
+            if (forms.delSelectItem.receiveType === 'CLASS') {
+              const tempClass = selectList.class || []
+              const sIndex = tempClass.findIndex(
+                (item: any) => item.id === forms.delSelectItem.receiveId
+              )
+              tempClass.splice(sIndex, 1)
+            } else if (forms.delSelectItem.receiveType === 'SCHOOL') {
+              const tempSchool = selectList.school || []
+              const sIndex = tempSchool.findIndex(
+                (item: any) => item.id === forms.delSelectItem.receiveId
+              )
+              tempSchool.splice(sIndex, 1)
+            } else if (forms.delSelectItem.receiveType === 'TEACHER') {
+              const tempTeacher = selectList.teacher || []
+              const sIndex = tempTeacher.findIndex(
+                (item: any) => item.id === forms.delSelectItem.receiveId
+              )
+              tempTeacher.splice(sIndex, 1)
+            } else if (forms.delSelectItem.receiveType === 'STUDENT') {
+              const tempStudent = selectList.student || []
+              const sIndex = tempStudent.findIndex(
+                (item: any) => item.id === forms.delSelectItem.receiveId
+              )
+              tempStudent.splice(sIndex, 1)
+            }
+            forms.selectList = selectList
+            console.log(forms.selectList, 'forms.selectList')
+
+            const index = forms.receives.findIndex(
+              (item: any) => item.receiveId === forms.delSelectItem.receiveId
+            )
+            forms.receives.splice(index, 1)
+          }}
+        />
       </div>
     )
   }

+ 43 - 0
src/school/mass-message/index.module.less

@@ -46,3 +46,46 @@
     padding-bottom: 4px;
   }
 }
+
+.create-message {
+  overflow: hidden;
+  --van-tab-active-text-color: var(--van-primary-color);
+  --van-tab-text-color: #333;
+  --van-tab-font-size: 16px;
+  :global {
+    .van-tab {
+      font-weight: 400;
+    }
+    .van-tabs__wrap {
+      // padding-bottom: 3px;
+    }
+  }
+}
+.cellGroup {
+  margin-top: 12px;
+  margin-bottom: 12px;
+  --van-uploader-size: 94px;
+  :global {
+    .van-cell {
+      padding: 18px 15px;
+      font-size: 16px;
+      color: #333333;
+    }
+  }
+}
+
+.receives {
+  padding-top: 6px !important;
+  padding-bottom: 6px !important;
+  &:last-child {
+    padding-bottom: 18px !important;
+  }
+
+  .img {
+    width: 40px;
+    height: 40px;
+    overflow: hidden;
+    border-radius: 50%;
+    margin-right: 12px;
+  }
+}

+ 83 - 38
src/school/mass-message/index.tsx

@@ -2,9 +2,11 @@ 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 { snedStatus } from '@/constant'
 import request from '@/helpers/request'
+import item from '@/student/coupons/item'
 import { Cell, CellGroup, Icon, List, Tab, Tabs } from 'vant'
-import { defineComponent, reactive } from 'vue'
+import { defineComponent, onMounted, reactive } from 'vue'
 import { useRouter } from 'vue-router'
 import styles from './index.module.less'
 
@@ -12,39 +14,71 @@ export default defineComponent({
   name: 'mass-message',
   setup() {
     const router = useRouter()
+    const status = sessionStorage.getItem('mass-message-send')
     const state = reactive({
       list: [],
       dataShow: true, // 判断是否有数据
       loading: false,
       finished: false,
+      tabValue: status || 'WAIT',
       params: {
-        search: null,
+        keyword: null as any,
+        sendStatus: status || 'WAIT',
         page: 1,
         rows: 10
-      }
+      },
+      isClick: false
     })
     const getList = async () => {
       try {
-        // const res = await request.post(url, {
-        //   data: {
-        //     ...params
-        //   }
-        // })
-        // state.loading = false
-        // const result = res.data || {}
+        if (state.isClick) return
+        state.isClick = true
+        const res = await request.post('/api-school/imMessageBatchSending/page', {
+          data: {
+            ...state.params
+          }
+        })
+        state.isClick = false
+        state.loading = false
+        const result = res.data || {}
         // 处理重复请求数据
-        // if (state.list.length > 0 && result.current === 1) {
-        //   return
-        // }
-        // state.list = state.list.concat(result.rows || [])
-        // state.finished = result.current >= result.totalPage
-        // state.params.page = result.current + 1
-        // state.dataShow = state.list.length > 0
+        if (state.list.length > 0 && result.current === 1) {
+          return
+        }
+        state.list = state.list.concat(result.rows || [])
+        state.finished = result.current >= result.pages
+        state.params.page = result.current + 1
+        state.dataShow = state.list.length > 0
       } catch {
-        // state.dataShow = false
-        // state.finished = true
+        state.isClick = false
+        state.dataShow = false
+        state.finished = true
       }
     }
+
+    // 搜索
+    const onSearch = () => {
+      state.params.page = 1
+      state.list = []
+      state.dataShow = true // 判断是否有数据
+      state.loading = false
+      state.finished = false
+      getList()
+    }
+
+    // 查看详情
+    const onDetail = async (item: any) => {
+      router.push({
+        path: '/create-message',
+        query: {
+          id: item.id
+        }
+      })
+    }
+
+    onMounted(() => {
+      getList()
+    })
     return () => (
       <div class={styles.massMessage}>
         <OSticky position="top">
@@ -62,9 +96,17 @@ export default defineComponent({
               )
             }}
           </OHeader>
-          <Tabs lineWidth={18}>
-            <Tab title="待发送"></Tab>
-            <Tab title="已发送"></Tab>
+          <Tabs
+            lineWidth={18}
+            v-model:active={state.tabValue}
+            onChange={(val: string) => {
+              state.params.sendStatus = val
+              onSearch()
+              sessionStorage.setItem('mass-message-send', val)
+            }}
+          >
+            <Tab title="待发送" name="WAIT"></Tab>
+            <Tab title="已发送" name="SEND"></Tab>
           </Tabs>
 
           <OSearch
@@ -73,6 +115,8 @@ export default defineComponent({
             placeholder="请输入群聊/学员名称"
             onSearch={(val: string) => {
               console.log('val', val)
+              state.params.keyword = val
+              onSearch()
             }}
           />
         </OSticky>
@@ -86,22 +130,23 @@ export default defineComponent({
             onLoad={getList}
             immediateCheck={false}
           >
-            <CellGroup inset>
-              <Cell class={styles.waitSend} titleStyle={{ flex: '1 auto' }}>
-                {{
-                  title: () => (
-                    <div class={styles.time}>
-                      <Icon name="clock-o" class={styles.clockO} />
-                      2022年10月31日 13:00:00
-                    </div>
-                  ),
-                  value: () => <span>待发送</span>
-                }}
-              </Cell>
-              <Cell valueClass={styles.messageContent}>
-                乐团今日的训练课程由于学校举办运动会延至明天进行,请同学们按时参加运动会,明天带好乐器下午三点准时到教室训练。最近天气转凉了,大家也记得多穿点衣服,不要冻感冒了。
-              </Cell>
-            </CellGroup>
+            {state.list.map((item: any) => (
+              <CellGroup inset onClick={() => onDetail(item)} style={{ marginBottom: '12px' }}>
+                <Cell class={styles.waitSend} titleStyle={{ flex: '1 auto' }}>
+                  {{
+                    title: () => (
+                      <div class={styles.time}>
+                        <Icon name="clock-o" class={styles.clockO} />
+                        {item.sendStatus === 'WAIT' ? item.createTime : ''}
+                        {item.sendStatus === 'SEND' ? item.sendTime : ''}
+                      </div>
+                    ),
+                    value: () => <span>{snedStatus[item.sendStatus]}</span>
+                  }}
+                </Cell>
+                <Cell valueClass={styles.messageContent}>{item.textMessage}</Cell>
+              </CellGroup>
+            ))}
           </List>
         ) : (
           <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无群发消息" />

+ 98 - 0
src/school/mass-message/select-sned.tsx

@@ -0,0 +1,98 @@
+import OSticky from '@/components/o-sticky'
+import { useRect } from '@vant/use'
+import { Button, Sticky, Tab, Tabs } from 'vant'
+import { defineComponent, onMounted, reactive, ref, watch } from 'vue'
+import ClassList from './component/class-list'
+import ManageList from './component/manage-list'
+import StudentList from './component/student-list'
+import TeacherList from './component/teacher-list/teacher-list'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'select-send',
+  props: {
+    selectList: {
+      type: Object,
+      default: {}
+    },
+    selectStatus: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['close', 'confirm', 'update:selectList'],
+  setup(props, { emit, expose }) {
+    const state = reactive({
+      height: 'auto' as any,
+      tabValue: 'class',
+      selectClass: [] as any,
+      selectStudent: [] as any,
+      selectTeacher: [] as any,
+      selectManage: [] as any
+    })
+
+    const onSubmit = async () => {
+      const params = {
+        class: state.selectClass,
+        student: state.selectStudent,
+        teacher: state.selectTeacher,
+        school: state.selectManage
+      }
+      emit('close')
+      emit('update:selectList', params)
+      emit('confirm', params)
+    }
+
+    watch(
+      () => props.selectList,
+      () => {
+        console.log('watch', props.selectList)
+        resetSelectItems()
+      },
+      { deep: true }
+    )
+
+    const resetSelectItems = () => {
+      const list = props.selectList || {}
+      state.selectClass = list.class || []
+      state.selectTeacher = list.teacher || []
+      state.selectManage = list.manage || []
+      state.selectStudent = list.student || []
+    }
+
+    onMounted(() => {
+      const { height } = useRect(document.querySelector('.van-tab') as HTMLElement)
+      state.height = height
+
+      resetSelectItems()
+      console.log(state, 'select')
+    })
+
+    return () => (
+      <div class={styles.orchestraDetail} style={{ background: '#f6f6f6', minHeight: '100vh' }}>
+        <Tabs sticky lineWidth={20} lineHeight={4} v-model:active={state.tabValue}>
+          <Tab title="班级" name="class">
+            <ClassList height={state.height} v-model:selectItem={state.selectClass} />
+          </Tab>
+          <Tab title="学员" name="student">
+            <StudentList height={state.height} v-model:selectItem={state.selectStudent} />
+          </Tab>
+          <Tab title="伴学老师" name="teacher">
+            <TeacherList height={state.height} v-model:selectItem={state.selectTeacher} />
+          </Tab>
+          <Tab title="管理老师" name="manage">
+            <ManageList height={state.height} v-model:selectItem={state.selectManage} />
+          </Tab>
+        </Tabs>
+
+        <OSticky position="bottom">
+          <div class={'btnGroup'}>
+            <Button round block type="primary" size="large" onClick={onSubmit}>
+              确认
+            </Button>
+          </div>
+        </OSticky>
+      </div>
+    )
+  }
+})

+ 1 - 0
src/school/orchestra/compontent/information.module.less

@@ -39,6 +39,7 @@
   .teacher {
     font-size: 18px;
     line-height: 24px;
+    max-width: 95px;
   }
 
   .className {

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

@@ -229,7 +229,9 @@ export default defineComponent({
                     <p class={styles.name}>在读学生</p>
                   </GridItem>
                   <GridItem>
-                    <p class={[styles.title, styles.teacher]}>{item.teacherName || '-'}</p>
+                    <p class={[styles.title, styles.teacher, 'van-ellipsis']}>
+                      {item.teacherName || '-'}
+                    </p>
                     <p class={styles.name}>伴学指导</p>
                   </GridItem>
                   <GridItem>

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

@@ -150,10 +150,10 @@ export default defineComponent({
                 {{
                   title: () => (
                     <div class={styles.oTitle}>
-                      {item.name}
+                      <div>{item.name}</div>
 
                       <Tag
-                        style={{ marginLeft: '6px' }}
+                        style={{ marginLeft: '6px', flexShrink: 0 }}
                         color={item.type === 'DELIVERY' ? '#FF8057' : '#64A9FF'}
                       >
                         {orchestraType[item.type]}

+ 1 - 0
src/school/orchestra/modal/teacher-list.module.less

@@ -13,6 +13,7 @@
     font-weight: 500;
     color: #333333;
     line-height: 22px;
+    max-width: 220px;
   }
 
   .class {

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

@@ -153,7 +153,7 @@ export default defineComponent({
                   icon: () => <Image class={styles.img} src={item.avatar || iconTeacher} />,
                   title: () => (
                     <div class={styles.content}>
-                      <p class={styles.name}>{item.nickname}</p>
+                      <p class={[styles.name, 'van-ellipsis']}>{item.nickname}</p>
                       <p class={styles.class}>
                         {item.subjectNames &&
                           item.subjectNames.map((subject: any) => (

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

@@ -112,7 +112,9 @@ export default defineComponent({
                     <div>
                       <div class={[styles.title, 'van-ellipsis']}>{item.title}</div>
                       <div class={[styles.content, 'van-multi-ellipsis--l2']}>{item.memo}</div>
-                      <div class={styles.time}>{dayjs(item.createBy).format('YYYY年MM月DD日')}</div>
+                      <div class={styles.time}>
+                        {dayjs(item.createTime).format('YYYY年MM月DD日')}
+                      </div>
                     </div>
                   )
                 }}

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

@@ -119,6 +119,7 @@
     font-size: 16px;
     font-weight: 600;
     color: #333333;
+    max-width: 120px;
   }
 
   .btn {

+ 33 - 18
src/school/train-planning/component/course-preview/index.tsx

@@ -260,27 +260,12 @@ export default defineComponent({
               ) : (
                 ''
               )}
-              {(item.conflictType && item.conflictType.includes('DIFF_SCHOOL_TEACHER')) ||
-              item.conflictType.includes('SAME_SCHOOL_TEACHER') ? (
+              {item.conflictType && item.conflictType.includes('DIFF_SCHOOL_TEACHER') ? (
                 <Tag
                   class={styles.conflict}
                   color="#F44541"
                   onClick={() => {
-                    const type = item.conflictType || []
-                    if (
-                      type.includes('DIFF_SCHOOL_TEACHER') &&
-                      type.includes('SAME_SCHOOL_TEACHER')
-                    ) {
-                      // 老师冲突 SAME_SCHOOL_TEACHER
-                      // 学校冲突 DIFF_SCHOOL_TEACHER
-
-                      state.conflictMessage =
-                        '该时间段伴学指导在其他学校有课 \n 伴学指导在本学校时间有冲突'
-                    } else if (type.includes('DIFF_SCHOOL_TEACHER')) {
-                      state.conflictMessage = '该时间段伴学指导在其他学校有课'
-                    } else if (type.includes('SAME_SCHOOL_TEACHER')) {
-                      state.conflictMessage = '伴学指导在本学校时间有冲突'
-                    }
+                    state.conflictMessage = '该时间段伴学指导在其他学校有课'
                     state.conflictStatus = true
                     state.choiceCourse = item
                   }}
@@ -290,13 +275,43 @@ export default defineComponent({
               ) : (
                 ''
               )}
+              {item.conflictType && item.conflictType.includes('SAME_SCHOOL_TEACHER') ? (
+                <Tag
+                  class={styles.conflict}
+                  color="#F44541"
+                  onClick={() => {
+                    state.conflictMessage = '伴学指导在本学校时间有冲突'
+                    state.conflictStatus = true
+                    state.choiceCourse = item
+                  }}
+                >
+                  老师冲突
+                </Tag>
+              ) : (
+                ''
+              )}
+              {item.conflictType && item.conflictType.includes('LEAVE') ? (
+                <Tag
+                  class={styles.conflict}
+                  color="#F44541"
+                  onClick={() => {
+                    state.conflictMessage = '伴学指导请假冲突'
+                    state.conflictStatus = true
+                    state.choiceCourse = item
+                  }}
+                >
+                  请假冲突
+                </Tag>
+              ) : (
+                ''
+              )}
             </div>
             <Cell center class={styles.cellTeacher}>
               {{
                 icon: () => <Image src={item.teacherAvatar || iconTeacher} class={styles.img} />,
                 title: () => (
                   <div class={styles.teacherInfo}>
-                    <p class={styles.teacherName}>{item.teacherName}</p>
+                    <p class={[styles.teacherName, 'van-ellipsis']}>{item.teacherName}</p>
                     <Tag type="primary">{item.className}</Tag>
                   </div>
                 ),

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

@@ -200,6 +200,7 @@ export default defineComponent({
                 v-model={item.trainTimer}
                 formatter={onFormatterInt}
                 maxlength={3}
+                type="tel"
               >
                 {{ extra: () => '分钟' }}
               </Field>
@@ -226,6 +227,7 @@ export default defineComponent({
                 v-model={item.times}
                 formatter={onFormatterInt}
                 maxlength={2}
+                type="tel"
               >
                 {{ extra: () => '课时' }}
               </Field>
@@ -268,12 +270,13 @@ export default defineComponent({
           v-model:modelValue={forms.classStatus}
           position="bottom"
           style={{ background: '#f6f6f6' }}
+          destroy
         >
           <PracticeClass
             onClose={() => (forms.classStatus = false)}
             classType={f.selectItem.classType}
+            selectItem={f.selectItem.classIdList}
             onConfirm={(val: any) => {
-              console.log(val, 'val')
               f.selectItem.classIdList = val
             }}
           />

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

@@ -151,7 +151,7 @@ export default defineComponent({
     }
 
     onMounted(() => {
-      getList()
+      getList(forms.calendarDate || new Date())
       getClasses()
       getStandardCourseNum()
     })

+ 12 - 0
src/school/train-planning/modal/calendar/index.tsx

@@ -81,6 +81,17 @@ export default defineComponent({
       return dayjs(this.currentDate).format('YYYY-MM-DD')
     }
   },
+  watch: {
+    calendarDate() {
+      // 初始化标题和最大显示日期
+      this.subtitle = dayjs(this.calendarDate || new Date()).format('YYYY年MM月')
+      this.maxDate = dayjs(this.calendarDate || new Date())
+        .endOf('month')
+        .toDate()
+      this.minDate = dayjs(this.calendarDate || new Date()).toDate()
+      this.currentDate = dayjs(this.calendarDate || new Date()).toDate()
+    }
+  },
   mounted() {
     // 初始化标题和最大显示日期
     this.subtitle = dayjs(this.calendarDate || new Date()).format('YYYY年MM月')
@@ -88,6 +99,7 @@ export default defineComponent({
       .endOf('month')
       .toDate()
     this.minDate = dayjs(this.calendarDate || new Date()).toDate()
+    this.currentDate = dayjs(this.calendarDate || new Date()).toDate()
 
     console.log(this.list, 'this.list')
     console.log(this.calendarDate, 'calendarDate')

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

@@ -32,6 +32,7 @@
       font-size: 14px;
       color: #777777;
       line-height: 20px;
+      max-width: 230px;
     }
   }
 

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

@@ -94,7 +94,7 @@ export default defineComponent({
                     <i></i>
                     {item.name}
                   </div>
-                  <div class={styles.name}>{item.orchestraName}</div>
+                  <div class={[styles.name, 'van-ellipsis']}>{item.orchestraName}</div>
                 </div>
               ),
               value: () => <span class={styles.teacherName}>{item.teacherName}</span>

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

@@ -40,10 +40,15 @@
   .teacherName {
     display: flex;
     align-items: center;
-    font-size: 16px;
-    font-weight: 600;
-    color: #333333;
-    line-height: 22px;
+
+    .name {
+      padding-top: 0;
+      max-width: 120px;
+      font-size: 16px;
+      font-weight: 600;
+      color: #333333;
+      line-height: 22px;
+    }
     :global {
       .van-tag {
         margin-left: 6px;
@@ -59,6 +64,7 @@
     font-size: 12px;
     color: #777777;
     line-height: 17px;
+    max-width: 200px;
   }
   .title {
     font-size: 24px;

+ 6 - 5
src/school/train-planning/modal/practice-class/index.tsx

@@ -54,7 +54,6 @@ export default defineComponent({
         finished: false
       },
       params: {
-        type: null,
         page: 1,
         rows: 20
       },
@@ -99,8 +98,7 @@ export default defineComponent({
         forms.isClick = true
         const { data } = await request.post('/api-school/classGroup/page', {
           data: {
-            page: 1,
-            rows: 20,
+            ...forms.params,
             schoolId: baseState.user.data.school.id,
             orchestraId: forms.orchestraId,
             classType: props.classType
@@ -189,9 +187,12 @@ export default defineComponent({
                       title: () => (
                         <div class={styles.content}>
                           <div class={styles.teacherName}>
-                            {item.teacherName} <Tag type="primary">{item.name}</Tag>
+                            <div class={[styles.name, 'van-ellipsis']}>{item.teacherName}</div>
+                            <Tag type="primary">{item.name}</Tag>
+                          </div>
+                          <div class={[styles.orchestraName, 'van-ellipsis']}>
+                            {item.orchestraName}
                           </div>
-                          <div class={styles.orchestraName}>{item.orchestraName}</div>
                         </div>
                       ),
                       value: () => (

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

@@ -45,7 +45,7 @@ export default defineComponent({
     }
 
     const onFilter = (type: any, option: any) => {
-      console.log(type, option)
+      // console.log(type, option)
       // 时间
       if (type === 'hour') {
         const hour: any = []
@@ -56,7 +56,7 @@ export default defineComponent({
             }
           })
         })
-        console.log(hour, 'hour')
+        // console.log(hour, 'hour')
         return hour
       }
       return option
@@ -65,7 +65,6 @@ export default defineComponent({
     // 时间切换时
     // [6:30, 12:00] [15:00, 18:00]
     const onChange = (val: any) => {
-      console.log(val, 'val')
       // 判断是否选择小时
       if (val.columnIndex === 1) return
 
@@ -168,8 +167,8 @@ export default defineComponent({
       const useFormat = onFormatTimer(useTimer)
       state.useTimerFormat = useFormat
       state.usedTimer = [...usedTimer]
-      console.log(onFormatTimer(useTimer), 'onFormatTimer')
-      console.log(state.useTimer, state.usedTimer, 'onUseTimer')
+      // console.log(onFormatTimer(useTimer), 'onFormatTimer')
+      // console.log(state.useTimer, state.usedTimer, 'onUseTimer')
 
       // 判断有可排课数据
       if (useFormat.length > 0) {

+ 1 - 1
src/state.ts

@@ -15,7 +15,7 @@ export const state = reactive({
     TEACHER: 'jmedu-teacher',
     SCHOOL: 'jmedu-school'
   },
-  platformApi: '/api-teacher' as '/api-student' | '/api-teacher' | '/api-school',
+  platformApi: '/api-student' as '/api-student' | '/api-teacher' | '/api-school',
   version: '', // 版本号 例如: 1.0.0
   ossUploadUrl: 'https://ks3-cn-beijing.ksyuncs.com/',
   musicCertStatus: false as boolean, // 是否音乐认证

+ 22 - 0
src/student/main.ts

@@ -8,6 +8,8 @@ import 'normalize.css'
 import '../styles/index.less'
 import { promisefiyPostMessage } from '@/helpers/native-message';
 import { setAuth } from './music-group/layout/utils';
+import { browser } from '@/helpers/utils';
+import { state } from '@/state';
 
 // 获取token
 promisefiyPostMessage({ api: 'getToken' }).then((res: any) => {
@@ -18,6 +20,26 @@ promisefiyPostMessage({ api: 'getToken' }).then((res: any) => {
   }
 })
 
+const paymentType = (window as any).paymentType // 浏览器设置
+if (browser().isTeacher || paymentType === 'TEACHER') {
+  state.platformType = 'TEACHER'
+} else if (browser().isStudent || paymentType === 'STUDENT') {
+  state.platformType = 'STUDENT'
+} else if (browser().isSchool || paymentType === 'SCHOOL') {
+  state.platformType = 'SCHOOL'
+} else {
+  state.platformType = 'STUDENT'
+}
+if (state.platformType === 'TEACHER') {
+  state.platformApi = '/api-teacher'
+} else if (state.platformType === 'SCHOOL') {
+  state.platformApi = '/api-school'
+} else if (state.platformType === 'STUDENT') {
+  state.platformApi = '/api-student'
+} else {
+  state.platformApi = '/api-student'
+}
+
 
 // import Vconsole from 'vconsole'
 // const vconsole = new Vconsole()

+ 2 - 4
src/student/member-center/index.tsx

@@ -236,7 +236,7 @@ export default defineComponent({
             <div class={styles['system-list']}>
               <div class={[styles['system-item'], styles.active]}>
                 <p class={[styles.title, 'van-hairline--bottom']}>
-                  {memberType[this.selectMember.cardType]}
+                  半年会员
                   <span>(6个月)</span>
                 </p>
                 <div class={styles.priceGroup}>
@@ -254,9 +254,7 @@ export default defineComponent({
 
           <div class={[styles.intro]}>
             <p>
-              酷乐秀会员可使用包括平台提供的所有训练乐谱,并专享“
-              <b>小酷Ai</b>
-              ”八大核心功能,孩子在家就能轻松完成乐器自主规范练习。
+              团练宝会员使用包括平台提供教材的所有训练乐谱,并专享“乐器练习云教练”八大核心功能,孩子在家就能轻松完成乐器自主规范练习。
             </p>
           </div>
 

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

@@ -29,6 +29,10 @@ export default defineComponent({
     schoolSystem: {
       type: String,
       default: 'sixYearSystem' // 默认为六年制
+    },
+    registerInfo: {
+      type: Object,
+      defualt: {}
     }
   },
   emits: ['next'],
@@ -252,6 +256,12 @@ export default defineComponent({
                   showToast('暂无报名声部')
                   return
                 }
+
+                // 切换订单时判断是否有支付中和已支付的订单,并且已注册过
+                if (props.registerInfo?.orderNumber > 0 && props.registerInfo?.register) {
+                  state.subjectChangeStatus = true
+                  return
+                }
                 state.subjectStatus = true
               }}
               rules={[{ required: true, message: '请选择声部' }]}
@@ -294,7 +304,7 @@ export default defineComponent({
           position="bottom"
           round
           safeAreaInsetBottom
-          // duration={0}
+          duration={0}
           lazyRender={false}
         >
           <Picker
@@ -310,7 +320,7 @@ export default defineComponent({
           />
         </Popup>
         {/* 班级 */}
-        <Popup v-model:show={state.classStatus} position="bottom" round>
+        <Popup v-model:show={state.classStatus} position="bottom" round duration={0}>
           <Picker
             showToolbar
             columns={state.classList}
@@ -324,7 +334,7 @@ export default defineComponent({
           />
         </Popup>
         {/* 声部 */}
-        <Popup v-model:show={state.subjectStatus} position="bottom" round>
+        <Popup v-model:show={state.subjectStatus} position="bottom" round duration={0}>
           <Picker
             showToolbar
             columns={state.subjectList}
@@ -346,14 +356,14 @@ export default defineComponent({
           cancelButtonText="选错了"
           showCancelButton
           onConfirm={() => {
-            console.log('223')
+            emit('next', 'order')
           }}
         >
           {{
             title: () => (
               <div class={styles.dialogTitle}>
                 <i></i>
-                课程冲突
+                修改声部
               </div>
             )
           }}

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

@@ -121,7 +121,7 @@ export default defineComponent({
         const refundReason = form.resionList.find((item: any) => item.value === form.checked)
         console.log({
           merOrderNo: form.refundSelect.orderNo,
-          refundReason: refundReason.text
+          refundReason: form.checked === 999 ? form.resion : refundReason.text
         })
         // return
         await request.post('/api-student/userPaymentOrder/refundPayment', {

+ 3 - 4
src/student/music-group/pre-apply/component/payment.tsx

@@ -88,7 +88,6 @@ export default defineComponent({
         paymentOrderDetails.forEach((item: any) => {
           state.paymentOrderDetails.push(item.goodsType)
         })
-
         // 初始化数据商品数据
         const details = data.details || []
         details.forEach((item: any) => {
@@ -244,8 +243,8 @@ export default defineComponent({
             paymentCashAmount: state.orderInfo.needPrice || 0,
             paymentCouponAmount: 0,
             goodsInfos: params,
-            orderName: '会员购买',
-            orderDesc: '会员购买'
+            orderName: '乐团报名缴费',
+            orderDesc: '乐团报名缴费'
           }
         })
 
@@ -266,8 +265,8 @@ export default defineComponent({
 
     onMounted(() => {
       // 查询未支付订单
-      paymentOrderUnpaid()
       registerGoods()
+      paymentOrderUnpaid()
     })
     return () => (
       <>

+ 25 - 2
src/student/music-group/pre-apply/index.tsx

@@ -87,6 +87,20 @@ export default defineComponent({
       }
     }
 
+    const getRegisterInfo = async (val: string) => {
+      // 重新查询状态
+      if (val === 'apply') {
+        try {
+          const { data } = await request.get(
+            '/api-student/orchestraRegister/registerStatus/' + route.query.id
+          )
+          state.registerInfo = data || {}
+        } catch {
+          //
+        }
+      }
+    }
+
     // 先请求接口
     getRegisterStatus()
 
@@ -103,7 +117,12 @@ export default defineComponent({
           </span>
         </div>
         <Sticky position="top">
-          <Tabs lineWidth={20} lineHeight={4} v-model:active={state.tabValue}>
+          <Tabs
+            lineWidth={20}
+            lineHeight={4}
+            v-model:active={state.tabValue}
+            onChange={(val: any) => getRegisterInfo(val)}
+          >
             <Tab title="报名信息" name="apply" disabled={state.purchase}></Tab>
             <Tab title="缴费信息" name="payment" disabled={state.purchase || !state.register}></Tab>
             <Tab title="我的订单" name="order" disabled={!state.register}></Tab>
@@ -111,7 +130,11 @@ export default defineComponent({
         </Sticky>
 
         {state.tabValue === 'apply' && (
-          <Apply onNext={onNext} schoolSystem={state.registerInfo.schoolSystem} />
+          <Apply
+            onNext={onNext}
+            registerInfo={state.registerInfo}
+            schoolSystem={state.registerInfo.schoolSystem}
+          />
         )}
         {state.tabValue === 'payment' && <Payment onNext={onNext} />}
         {state.tabValue === 'order' && <Order onNext={onNext} />}

+ 17 - 7
src/student/music-group/pre-apply/order-detail.tsx

@@ -28,8 +28,8 @@ export default defineComponent({
       config: route.query.config ? JSON.parse(route.query.config as any) : {},
       hasFreight: route.query.hf ? false : true, // 是否显示
       freight: '', // 运费
-
-      agreeStatus: true //是否勾选协议
+      agreeStatus: true, //是否勾选协议
+      showHeader: false
     })
 
     const orderType = computed(() => {
@@ -155,8 +155,8 @@ export default defineComponent({
               hideLoading: true
             }
           )
-          console.log(data)
-          if (data.status !== 'WAIT_PAY') {
+          // console.log(data)
+          if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
             clearInterval(orderTimer)
             window.location.replace(
               window.location.origin +
@@ -172,7 +172,7 @@ export default defineComponent({
     }
 
     const beforeSubmit = () => {
-      const pt = state.config.paymentChannel
+      const pt = state.pay_channel
       let payCode = 'qrCode'
       // 判断当前浏览器
       if (browser().weixin) {
@@ -201,6 +201,7 @@ export default defineComponent({
     }
 
     const onSubmit = async () => {
+      clearInterval(state.orderTimer)
       if (orderType.value === 'VIP') {
         // onCallback()
         buyVip(onCallback)
@@ -258,6 +259,7 @@ export default defineComponent({
             receiveAddress: addressDetails.value.id
           }
         })
+        console.log(data)
         state.pay_channel = data.paymentChannel
         if (data.status === 'PAID') {
           showConfirmDialog({
@@ -290,6 +292,12 @@ export default defineComponent({
     }
 
     onMounted(() => {
+      if (browser().isApp) {
+        state.showHeader = true
+      } else {
+        state.showHeader = false
+      }
+
       // 获取收货地址
       let details = sessionStorage.getItem('addressDetails')
       details = details ? JSON.parse(details) : {}
@@ -339,7 +347,7 @@ export default defineComponent({
           <div class={styles.protocol}>
             <OProtocol
               v-model:modelValue={state.agreeStatus}
-              showHeader
+              showHeader={state.showHeader}
               style={{ paddingLeft: 0, paddingRight: 0 }}
             />
           </div>
@@ -396,7 +404,9 @@ export default defineComponent({
             {/* <i class={styles.codeClose} onClick={() => (state.showQrcode = false)}></i> */}
             <div class={styles.codeImg}>
               <div class={styles.codeContent}>
-                <h2 class={styles.codeTitle}>乐团报名</h2>
+                <h2 class={styles.codeTitle}>
+                  {orderType.value === 'VIP' ? '开通会员' : '乐团报名'}
+                </h2>
                 <div class={styles.codeName}>
                   请截图下方二维码 登录{state.pay_channel === 'wx_pub' ? '微信' : '支付宝'}
                   扫码支付

+ 29 - 5
src/student/my-orchestra/apply-withdrawal.tsx

@@ -4,21 +4,45 @@ import iconStudent from '@common/images/icon_student.png'
 import styles from './apply-withdrawal.module.less'
 import { Button, Dialog, Field, Image } from 'vant'
 import OSticky from '@/components/o-sticky'
+import { useRoute } from 'vue-router'
+import request from '@/helpers/request'
+import { state } from '@/state'
 
 export default defineComponent({
   name: 'apply-withdrawal',
   setup() {
+    const route = useRoute()
     const headColor = reactive({
       headBg: 'transparent',
       textColor: '#fff'
     })
-    const state = reactive({
-      status: false
+    const forms = reactive({
+      status: false,
+      reason: null,
+      id: route.query.id
     })
 
+    const getDetails = async () => {
+      try {
+        const { data } = await request.post('/api-student/student/getOrchestraDate/' + forms.id)
+      } catch {
+        //
+      }
+    }
+
     const onSubmit = async () => {
-      state.status = true
+      // forms.status = true
+      await request.post('/api-student/orchestra/leaveOrchestra', {
+        data: {
+          studentId: state.user.data.account.id,
+          orchestraId: forms.id,
+          reason: forms.reason
+        }
+      })
     }
+    onMounted(() => {
+      getDetails()
+    })
     return () => (
       <>
         <div class={styles.headers}>
@@ -49,7 +73,7 @@ export default defineComponent({
             </div>
             <div class={styles.nums}>0/400</div>
           </div>
-          <Field placeholder="请输入退团详细原因" type="textarea" rows={3} />
+          <Field v-model={forms.reason} placeholder="请输入退团详细原因" type="textarea" rows={3} />
         </div>
 
         <OSticky position="bottom">
@@ -61,7 +85,7 @@ export default defineComponent({
         </OSticky>
 
         <Dialog
-          v-model:show={state.status}
+          v-model:show={forms.status}
           message={'确定要提交退团申请吗?'}
           messageAlign="left"
           confirmButtonText="确定"

+ 6 - 0
src/student/my-orchestra/index.module.less

@@ -103,6 +103,12 @@
           margin-left: 6px;
         }
       }
+      .name {
+        font-size: 0.42667rem;
+        font-weight: 600;
+        padding-top: 0;
+        max-width: 120px;
+      }
     }
     .classCheckbox {
       display: flex;

+ 173 - 95
src/student/my-orchestra/index.tsx

@@ -1,5 +1,5 @@
 import OHeader from '@/components/o-header'
-import { Cell, CellGroup, Grid, GridItem, Image, List, Tag } from 'vant'
+import { Cell, CellGroup, Grid, GridItem, Image, List, Picker, Popup, Tag } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import styles from './index.module.less'
@@ -27,8 +27,9 @@ export default defineComponent({
         page: 1,
         rows: 20
       },
-      selectItem: {} as any,
-      selectType: 'add'
+      orchestraStatus: false,
+      orchestraList: [] as any,
+      selectOrchestra: {} as any
     })
     const onSearch = () => {
       state.params.page = 1
@@ -47,7 +48,7 @@ export default defineComponent({
         const res = await request.post('/api-student/orchestraPhotoAlbum/page', {
           data: {
             ...state.params,
-            orchestraId: route.query.id
+            orchestraId: state.selectOrchestra.orchestraId
           }
         })
         state.listState.loading = false
@@ -79,116 +80,193 @@ export default defineComponent({
       })
     }
 
-    onMounted(() => {
-      getList()
+    const getOrchestras = async () => {
+      try {
+        const { data } = await request.post('/api-student/orchestra/studentOrchestra', {})
+        console.log(data)
+        state.orchestraList = data || []
+        if (data && data.length > 0) {
+          state.selectOrchestra = data[0]
+        }
+      } catch {
+        //
+      }
+    }
+
+    const onMessage = async (item: any) => {
+      console.log(item)
+      postMessage({
+        api: 'joinChatGroup',
+        content: {
+          type: 'multi', // single 单人 multi 多人
+          id: item.classGroupId
+        }
+      })
+    }
+
+    onMounted(async () => {
+      await getOrchestras()
+      await getList()
     })
     return () => (
       <div class={styles.myOrchestra}>
         <OHeader
           v-slots={{
-            right: () => (
-              <span
-                onClick={() => {
-                  router.push('/apply-withdrawal')
-                }}
-              >
-                申请退团
-              </span>
-            )
+            right: () =>
+              state.selectOrchestra.orchestraId && (
+                <span
+                  onClick={() => {
+                    router.push({
+                      path: '/apply-withdrawal',
+                      query: {
+                        id: state.selectOrchestra.orchestraId
+                      }
+                    })
+                  }}
+                >
+                  申请退团
+                </span>
+              )
           }}
         />
-
-        <CellGroup inset class={styles.oList}>
-          <Cell center clickable>
-            {{
-              title: () => (
-                <div class={styles.orchestra}>
-                  <i></i>
-                  <span class="van-ellipsis">
-                    武汉小学2022标准团武汉小学2022标准团武汉小学2022标准团
-                  </span>
-                </div>
-              ),
-              value: () => (
-                <div class={styles.iconChange}>
-                  切换乐团 <Image src={iconChange} class={styles.img} />
-                </div>
-              )
-            }}
-          </Cell>
-        </CellGroup>
-
-        <div class={[styles.gridContainer, styles.gridClass]}>
-          {[1, 2].map((item: any) => (
-            <CellGroup class={styles.classCellGroup}>
-              <Cell
-                center
-                titleStyle={{ flex: '0 auto' }}
-                valueClass={styles.classCheckbox}
-                border={false}
-              >
+        {state.orchestraList.length > 0 ? (
+          <>
+            <CellGroup inset class={styles.oList}>
+              <Cell center clickable>
                 {{
-                  icon: () => <Image src={iconTeacher} class={styles.img} />,
                   title: () => (
-                    <div class={styles.content}>
-                      <div class={styles.teacherName}>
-                        1211212 <Tag type="primary">长笛班</Tag>
-                      </div>
+                    <div class={styles.orchestra}>
+                      <i></i>
+                      <span class="van-ellipsis">{state.selectOrchestra.orchestraName}</span>
                     </div>
                   ),
-                  value: () => <Image class={styles.messageImg} src={iconMessage} />
+                  value: () =>
+                    state.selectOrchestra.classGroupIdList &&
+                    state.selectOrchestra.classGroupIdList.length > 0 && (
+                      <div
+                        class={styles.iconChange}
+                        onClick={() => {
+                          state.orchestraStatus = true
+                        }}
+                      >
+                        切换乐团 <Image src={iconChange} class={styles.img} />
+                      </div>
+                    )
                 }}
               </Cell>
-              <Grid border={false} columnNum={3}>
-                <GridItem>
-                  <p class={styles.title}>1/2</p>
-                  <p class={styles.name}>课程</p>
-                </GridItem>
-                <GridItem>
-                  <p class={[styles.title]}>1/2</p>
-                  <p class={styles.name}>课后练习</p>
-                </GridItem>
-                <GridItem>
-                  <p class={styles.title}>1/2</p>
-                  <p class={styles.name}>单元测试</p>
-                </GridItem>
-              </Grid>
             </CellGroup>
-          ))}
-        </div>
-
-        <div class={styles.title}>
-          <i></i>训练照片
-        </div>
+            <div class={[styles.gridContainer, styles.gridClass]}>
+              {state.selectOrchestra.classGroupIdList &&
+                state.selectOrchestra.classGroupIdList.map((item: any) => (
+                  <CellGroup class={styles.classCellGroup}>
+                    <Cell
+                      center
+                      titleStyle={{ flex: '0 auto' }}
+                      valueClass={styles.classCheckbox}
+                      border={false}
+                    >
+                      {{
+                        icon: () => (
+                          <Image src={item.teacherAvatar || iconTeacher} class={styles.img} />
+                        ),
+                        title: () => (
+                          <div class={styles.content}>
+                            <div class={styles.teacherName}>
+                              <span class={[styles.name, 'van-ellipsis']}>{item.teacherName}</span>
+                              <Tag type="primary">{item.classGroupName}</Tag>
+                            </div>
+                          </div>
+                        ),
+                        value: () => (
+                          <Image
+                            class={styles.messageImg}
+                            src={iconMessage}
+                            onClick={() => onMessage(item)}
+                          />
+                        )
+                      }}
+                    </Cell>
+                    <Grid border={false} columnNum={3}>
+                      <GridItem>
+                        <p class={styles.title}>
+                          {item.completeCourseNum || 0}/{item.totalCourseNum || 0}
+                        </p>
+                        <p class={styles.name}>课程</p>
+                      </GridItem>
+                      <GridItem>
+                        <p class={[styles.title]}>
+                          {item.completeTrainingNum || 0}/{item.totalTrainingNum || 0}
+                        </p>
+                        <p class={styles.name}>课后练习</p>
+                      </GridItem>
+                      <GridItem>
+                        <p class={styles.title}>
+                          {item.completeUnitTestNum || 0}/{item.totalUnitTestNum || 0}
+                        </p>
+                        <p class={styles.name}>单元测试</p>
+                      </GridItem>
+                    </Grid>
+                  </CellGroup>
+                ))}
+            </div>
+            <div class={styles.title}>
+              <i></i>训练照片
+            </div>
+            {state.listState.dataShow ? (
+              <List
+                v-model:loading={state.listState.loading}
+                finished={state.listState.finished}
+                finishedText=" "
+                onLoad={getList}
+                immediateCheck={false}
+              >
+                <div class={styles.phoneContainer}>
+                  {state.list.map((item: any) => (
+                    <div class={styles.item} onClick={() => onDetail(item)}>
+                      {item.coverUrl ? (
+                        <Image class={styles.img} src={item.coverUrl} />
+                      ) : (
+                        <div class={[styles.img, styles.default]}>
+                          <Image src={iconPhoneDefaut} class={styles.defaultImg} />
+                        </div>
+                      )}
 
-        {state.listState.dataShow ? (
-          <List
-            v-model:loading={state.listState.loading}
-            finished={state.listState.finished}
-            finishedText=" "
-            onLoad={getList}
-            immediateCheck={false}
-          >
-            <div class={styles.phoneContainer}>
-              {state.list.map((item: any) => (
-                <div class={styles.item} onClick={() => onDetail(item)}>
-                  {item.coverUrl ? (
-                    <Image class={styles.img} src={item.coverUrl} />
-                  ) : (
-                    <div class={[styles.img, styles.default]}>
-                      <Image src={iconPhoneDefaut} class={styles.defaultImg} />
+                      <p class={[styles.name, 'van-ellipsis']}>{item.name}</p>
+                      <p class={styles.num}>{item.photoCount}张</p>
                     </div>
-                  )}
-
-                  <p class={[styles.name, 'van-ellipsis']}>{item.name}</p>
-                  <p class={styles.num}>{item.photoCount}张</p>
+                  ))}
                 </div>
-              ))}
-            </div>
-          </List>
+              </List>
+            ) : (
+              <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无训练相片" />
+            )}
+          </>
         ) : (
-          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无相册" />
+          <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无乐团" />
         )}
+
+        <Popup v-model:show={state.orchestraStatus} position="bottom" round>
+          <Picker
+            columns={state.orchestraList}
+            columnsFieldNames={{ text: 'orchestraName', value: 'orchestraId' }}
+            onCancel={() => (state.orchestraStatus = false)}
+            onConfirm={(val: any) => {
+              const selectValue = val.selectedValues[0]
+              if (selectValue === state.selectOrchestra.orchestraId) {
+                state.orchestraStatus = false
+                return
+              }
+
+              state.orchestraList.forEach((item: any) => {
+                if (item.orchestraId === selectValue) {
+                  state.selectOrchestra = item
+                }
+              })
+
+              state.orchestraStatus = false
+            }}
+          />
+        </Popup>
       </div>
     )
   }

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

@@ -11,6 +11,7 @@ import request from '@/helpers/request'
 import { useRoute } from 'vue-router'
 import { orderStatus } from '@/constant'
 import { browser, moneyFormat } from '@/helpers/utils'
+import { useEventListener, useWindowScroll } from '@vueuse/core'
 
 export default defineComponent({
   name: 'payment-result',
@@ -18,7 +19,9 @@ export default defineComponent({
     const route = useRoute()
     const state = reactive({
       orders: {} as any,
-      goodsInfos: [] as any
+      goodsInfos: [] as any,
+      backIconColor: 'white' as any,
+      background: 'transparent'
     })
     const getDetails = async () => {
       try {
@@ -68,11 +71,27 @@ export default defineComponent({
 
     onMounted(() => {
       getDetails()
+      useEventListener(document, 'scroll', () => {
+        const { y } = useWindowScroll()
+        if (y.value > 52) {
+          state.background = '#fff'
+          state.backIconColor = 'black'
+        } else {
+          state.background = 'transparent'
+          state.backIconColor = 'white'
+        }
+      })
     })
     return () => (
       <div class={styles.paymentResult}>
         <div class={styles.paymentTitle}>
-          {browser().isApp && <OHeader border={false} background="transparent" />}
+          {browser().isApp && (
+            <OHeader
+              border={false}
+              background={state.background}
+              backIconColor={state.backIconColor}
+            />
+          )}
 
           {state.orders.id && (
             <>
@@ -90,7 +109,7 @@ export default defineComponent({
 
         <CellGroup inset class={styles.cellGroup}>
           <Cell>
-            {{ title: () => '付款时间', value: () => <span>{state.orders.createTime}</span> }}
+            {{ title: () => '付款时间', value: () => <span>{state.orders.payTime || '--'}</span> }}
           </Cell>
           <Cell>
             {{ title: () => '订单编号', value: () => <span>{state.orders.orderNo}</span> }}

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

@@ -161,7 +161,7 @@ export default defineComponent({
         await request.post('/api-student/userPaymentOrder/refundPayment', {
           data: {
             merOrderNo: form.refundSelect.orderNo,
-            refundReason: refundReason.text
+            refundReason: form.checked === 999 ? form.resion : refundReason.text
           }
         })
 
@@ -242,7 +242,7 @@ export default defineComponent({
                 <Cell style={'padding: 0'}>
                   {{
                     title: () =>
-                      item.status === 'PAID' && (
+                      item.refundable && (
                         <Button
                           block
                           class={styles.refundBtn}

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

@@ -114,7 +114,27 @@ export default defineComponent({
       })
     }
 
-    const onConfirmOrder = async (item: any) => {}
+    const onConfirmOrder = async (item: any) => {
+      try {
+        const { data } = await request.get('/api-student/userPaymentOrder/unpaid', {
+          params: {
+            orderNo: item.orderNo,
+            paymentType: item.orderType
+          }
+        })
+
+        const paymentConfig = data.paymentConfig
+        router.push({
+          path: '/orderDetail',
+          query: {
+            config: JSON.stringify(paymentConfig.paymentConfig),
+            orderNo: paymentConfig.orderNo
+          }
+        })
+      } catch {
+        //
+      }
+    }
 
     const onDetails = (item: any) => {
       router.push({
@@ -192,7 +212,10 @@ export default defineComponent({
                           size="small"
                           color="#777777"
                           class={styles.smallBtn}
-                          onClick={(item: any) => onCancelOrder(item)}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                            onCancelOrder(item)
+                          }}
                         >
                           取消订单
                         </Button>
@@ -200,9 +223,12 @@ export default defineComponent({
                           plain
                           round
                           size="small"
-                          color="#777777"
+                          type="primary"
                           class={styles.smallBtn}
-                          onClick={(item: any) => onConfirmOrder(item)}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                            onConfirmOrder(item)
+                          }}
                         >
                           继续支付
                         </Button>

+ 1 - 1
src/views/adapay/pay-define/index.tsx

@@ -38,7 +38,7 @@ export default defineComponent({
         if (state.pay_channel == 'wx_pub') {
           payMap.code = state.code
         }
-
+        console.log(payMap, 'payMap')
         const { data } = await request.post('/api-student/open/userOrder/executePayment', {
           data: {
             ...payMap

+ 2 - 0
src/views/adapay/pay-result/index.tsx

@@ -100,6 +100,8 @@ export default defineComponent({
             ? data.expend.qrcode_url + '?payment_id=' + data.id + '&pay_channel=' + data.pay_channel
             : data.expend.qrcode_url
         window.location.href = url
+      } else if (state.pay_channel == 'alipay_wap') {
+        window.location.href = data.expend.pay_info
       } else if (state.pay_channel == 'wx_pub') {
         const tempPayInfo = JSON.parse(data.expend.pay_info)
         state.payInfo = tempPayInfo

+ 67 - 16
src/views/courseList/index.tsx

@@ -1,10 +1,11 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Empty, Grid, GridItem, Icon } from 'vant'
+import { Button, Empty, Grid, GridItem, Icon, showToast } from 'vant'
 import { defineComponent, onMounted, reactive } 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'
 export default defineComponent({
   name: 'lessonCourseware',
   setup() {
@@ -14,14 +15,31 @@ export default defineComponent({
       list: [] as any
     })
     const getList = async () => {
-      try {
-        const res: any = await request.post(
-          state.platformApi + '/courseSchedule/myCoursewareDetail/' + route.query.id
-        )
-        if (Array.isArray(res?.data)) {
-          data.list = res.data
-        }
-      } catch (error) {}
+      if (route.query.courseScheduleId) {
+        try {
+          const res: any = await request.post(
+            state.platformApi + '/courseSchedule/getCoursewareDetail',
+            {
+              params: {
+                courseScheduleId: route.query.courseScheduleId,
+                coursewareId: route.query.id
+              }
+            }
+          )
+          if (Array.isArray(res?.data)) {
+            data.list = res.data
+          }
+        } catch (error) {}
+      } else {
+        try {
+          const res: any = await request.post(
+            state.platformApi + '/courseSchedule/myCoursewareDetail/' + route.query.id
+          )
+          if (Array.isArray(res?.data)) {
+            data.list = res.data
+          }
+        } catch (error) {}
+      }
     }
     onMounted(() => {
       getList()
@@ -30,6 +48,8 @@ export default defineComponent({
     const handleClick = (item: any) => {
       if (route.query.code === 'select') {
         console.log('选择课时')
+        setCoursewareDetail(item)
+        return
       }
       router.push({
         path: '/coursewarePlay',
@@ -38,6 +58,24 @@ export default defineComponent({
         }
       })
     }
+    // 绑定课时
+    const setCoursewareDetail = async (item: any) => {
+      try {
+        const res: any = await request.post(
+          state.platformApi + '/courseSchedule/setCoursewareDetail',
+          {
+            params: {
+              courseScheduleId: route.query.courseScheduleId,
+              coursewareDetailId: item.lessonCoursewareDetailId
+            }
+          }
+        )
+        if (res.code === 200) {
+          showToast('保存成功')
+          postMessage({ api: 'back' })
+        }
+      } catch (error) {}
+    }
     return () => (
       <div style={{ paddingTop: '14px' }}>
         <Grid gutter={14} columnNum={3} class={styles.grid}>
@@ -56,14 +94,19 @@ export default defineComponent({
                   {/* <img src={item.coverImg} class={styles.cover} /> */}
                   <div class={styles.title}>
                     <div>{item.coursewareDetailName}</div>
-                    <div>已使用 {item.useNum} 次</div>
+                    {route.query.code !== 'select' && <div>已使用 {item.useNum} 次</div>}
                   </div>
-                  <div class={styles.num}>
-                    查看
-                    <Icon name="play-circle-o" />
-                  </div>
-                  {item.unlock && (
-                    <div class={styles.look}>
+                  {route.query.code !== 'select' ? (
+                    <div class={styles.num}>
+                      查看
+                      <Icon name="play-circle-o" />
+                    </div>
+                  ) : (
+                    <div class={styles.num}>选择</div>
+                  )}
+
+                  {route.query.code == 'select' && !item.unlock && (
+                    <div class={styles.look} onClick={(e: Event) => e.stopPropagation()}>
                       <Icon name={iconLook} /> 未解锁
                     </div>
                   )}
@@ -72,6 +115,14 @@ export default defineComponent({
             )
           })}
         </Grid>
+        <Button
+          onClick={() => {
+            location.href =
+              'http://192.168.3.114:1000/teacher.html#/coursewarePlay?id=1610595720511209474'
+          }}
+        >
+          测试
+        </Button>
         {!data.list.length && <Empty description="空空如也" />}
       </div>
     )

+ 19 - 0
src/views/coursewarePlay/component/musicScore.module.less

@@ -0,0 +1,19 @@
+.musicScore {
+  position: relative;
+  height: 100%;
+  -webkit-overflow-scrolling: touch;
+  overflow: scroll;
+  .container {
+    display: block;
+    border: none;
+    width: 100vw;
+    height: 100vh;
+  }
+  .musicModel {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+  }
+}

+ 34 - 0
src/views/coursewarePlay/component/musicScore.tsx

@@ -0,0 +1,34 @@
+import { defineComponent, ref, nextTick } from 'vue'
+import styles from './musicScore.module.less'
+
+export default defineComponent({
+  name: 'musicScore',
+  props: {
+    music: {
+      type: Object,
+      default: () => {}
+    },
+  },
+  setup(props, {}) {
+    const Authorization = sessionStorage.getItem('Authorization') || ''
+    const dev = /(localhost|192)/.test(location.host)
+    console.log(dev, 'https://ponline.colexiu.com')
+    let src = `${dev ? `http://192.168.3.114:3000` : location.origin}/orchestra-music-score/#/?id=${
+      props.music.content
+    }&Authorization=${Authorization}&modelType=practice`
+    console.log('src', src)
+    
+    return () => (
+      <div
+        class={styles.musicScore}
+      >
+        <iframe
+          class={[styles.container, 'musicIframe']}
+          frameborder="0"
+          src={src}
+        ></iframe>
+        {/* <div class={styles.musicModel}></div> */}
+      </div>
+    )
+  }
+})

+ 25 - 0
src/views/coursewarePlay/component/point.module.less

@@ -0,0 +1,25 @@
+.container {
+  min-height: 100vh;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 0px 12Px 12Px 0px;
+  color: #fff;
+  font-size: 16Px;
+  padding: 38Px 20Px 20Px 40Px;
+  box-sizing: border-box;
+}
+.pointHead{
+    display: flex;
+    align-items: center;
+    padding-bottom: 10Px;
+    img{
+        width: 24Px;
+        height: 24Px;
+        margin-right: 8Px;
+    }
+}
+.item{
+    padding: 11px 0;
+}
+.itemActive{
+    color: var(--van-primary);
+}

+ 51 - 0
src/views/coursewarePlay/component/points.tsx

@@ -0,0 +1,51 @@
+import { defineComponent } from 'vue'
+import styles from './point.module.less'
+import iconMulv from '../image/icon-mulv.svg'
+export default defineComponent({
+  name: 'points',
+  props: {
+    data: {
+      type: Array,
+      default: () => []
+    },
+    active: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleSelect'],
+  setup(props, {emit}) {
+    return () => (
+      <div class={styles.container}>
+        <div>
+          <div class={styles.pointHead}>
+            <img src={iconMulv} />
+            课程目录
+          </div>
+          <div class={styles.items}>
+            {props.data.map((item: any) => {
+              return (
+                <>
+                  {Array.isArray(item?.materialList) &&
+                    item.materialList.map((n: any, index: number) => {
+                      return (
+                        <div
+                          class={[
+                            styles.item,
+                            `${item.id}-${n.id}` === props.active ? styles.itemActive : ''
+                          ]}
+                          onClick={() => emit('handleSelect', {id: item.id, index, active: `${item.id}-${n.id}`})}
+                        >
+                          {item.name}-{n.name}
+                        </div>
+                      )
+                    })}
+                </>
+              )
+            })}
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 22 - 0
src/views/coursewarePlay/image/icon-menu.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="M8.41697068,5.70140135 L20.095405,5.70140135 C20.5949959,5.70140135 21,5.29824534 21,4.80093423 C21,4.30362313 20.5949959,3.90046712 20.095405,3.90046712 L8.41697068,3.90046712 C7.91737983,3.90046712 7.51237569,4.30362313 7.51237569,4.80093423 C7.51237569,5.29824534 7.91737983,5.70140135 8.41697068,5.70140135 Z M8.41671626,12.883105 L20.0522954,12.883105 C20.5518863,12.883105 20.9568904,12.479949 20.9568904,11.9826379 C20.9568904,11.4853268 20.5518863,11.0821708 20.0522954,11.0821708 L8.41671626,11.0821708 C7.91712541,11.0821708 7.51212128,11.4853268 7.51212128,11.9826379 C7.51212128,12.479949 7.91712541,12.883105 8.41671626,12.883105 Z M20.095405,18.3036919 L8.41697068,18.3036919 C7.91737983,18.3036919 7.51237569,18.7068479 7.51237569,19.204159 C7.51237569,19.7014701 7.91737983,20.1046261 8.41697068,20.1046261 L20.095405,20.1046261 C20.5949959,20.1046261 21,19.7014701 21,19.204159 C21,18.7068479 20.5949959,18.3036919 20.095405,18.3036919 L20.095405,18.3036919 Z M2,4.80093423 C2,5.79556275 2.81000194,6.60186847 3.80918997,6.60186847 C4.808378,6.60186847 5.61837994,5.79556275 5.61837994,4.80093423 C5.61837994,3.80630572 4.808378,3 3.80918997,3 C2.81000194,3 2,3.80630572 2,4.80093423 Z M2,11.9718604 C2,12.9664889 2.81000194,13.7727946 3.80918997,13.7727946 C4.808378,13.7727946 5.61837994,12.9664889 5.61837994,11.9718604 C5.61837994,10.9772319 4.808378,10.1709262 3.80918997,10.1709262 C2.81000194,10.1709262 2,10.9772319 2,11.9718604 Z M2,19.1990658 C2,20.1936943 2.81000194,21 3.80918997,21 C4.808378,21 5.61837994,20.1936943 5.61837994,19.1990658 C5.61837994,18.2044373 4.808378,17.3981315 3.80918997,17.3981315 C2.81000194,17.3981315 2,18.2044373 2,19.1990658 Z" id="path-1"></path>
+        <filter x="-15.8%" y="-16.7%" width="131.6%" height="133.3%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" 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.125792177 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(-741.000000, -157.000000)" fill-rule="nonzero">
+            <g id="编组-4" transform="translate(734.000000, 147.000000)">
+                <g id="形状结合" transform="translate(7.000000, 10.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>

+ 18 - 0
src/views/coursewarePlay/image/icon-mulv.svg

@@ -0,0 +1,18 @@
+<?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>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#FFB790" offset="0%"></stop>
+            <stop stop-color="#FF8057" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="课件播放(侧边目录)" transform="translate(-63.000000, -40.000000)" fill-rule="nonzero">
+            <g id="课程目录" transform="translate(63.000000, 40.000000)">
+                <path d="M0,12 C0.0544564416,8.0225337 0.745221648,4.72912615 2.76493864,2.76493864 C4.72912615,0.745221648 8.0225337,0.0544564416 12,0 C15.9774663,0.0544564416 19.2708739,0.745221648 21.2350614,2.76493864 C23.2547784,4.72912615 23.9455436,8.0225337 24,12 C23.9455436,15.9774663 23.2547784,19.2708739 21.2350614,21.2350614 C19.2708739,23.2547784 15.9774663,23.9455436 12,24 C8.0225337,23.9455436 4.72912615,23.2547784 2.76493864,21.2350614 C0.745221648,19.2708739 0.0544564416,15.9774663 0,12 Z" id="路径" fill="url(#linearGradient-1)"></path>
+                <path d="M16.9005371,8.99518041 L7.09972174,8.99518041 C6.61722643,8.99518041 6.22222222,8.57140376 6.22222222,8.05314576 C6.22222222,7.53516565 6.61696758,7.11111111 7.09972174,7.11111111 L16.9002783,7.11111111 C17.3827736,7.11111111 17.7777778,7.53488776 17.7777778,8.05314576 C17.7777778,8.57112587 17.3830324,8.99518041 16.9005371,8.99518041 L16.9005371,8.99518041 Z M16.9005371,13.3863402 L7.09972174,13.3863402 C6.61722643,13.3863402 6.22222222,12.9625635 6.22222222,12.4443055 C6.22222222,11.9263254 6.61696758,11.5022709 7.09972174,11.5022709 L16.9002783,11.5022709 C17.3827736,11.5022709 17.7777778,11.9260475 17.7777778,12.4443055 C17.7777778,12.9625635 17.3830324,13.3863402 16.9005371,13.3863402 L16.9005371,13.3863402 Z M10.9332816,17.7777778 L7.09972174,17.7777778 C6.61722643,17.7777778 6.22222222,17.3540011 6.22222222,16.8357431 C6.22222222,16.317763 6.61696758,15.8937085 7.09972174,15.8937085 L10.9330227,15.8937085 C11.415518,15.8937085 11.8105224,16.3174851 11.8105224,16.8357431 C11.8107811,17.3540011 11.4157769,17.7777778 10.9332816,17.7777778 L10.9332816,17.7777778 Z" id="形状" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 62 - 21
src/views/coursewarePlay/index.module.less

@@ -3,10 +3,17 @@
   height: 100vh;
   background-color: rgba(89, 98, 126, 0.2);
 }
+.headerContainer {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 1;
+  padding: 10Px 10Px 0 40Px;
+  display: flex;
+  align-items: center;
+}
 .backBtn {
-  position: absolute;
-  left: 40px;
-  top: 10px;
   color: #fff;
   width: 40px;
   height: 26px;
@@ -16,11 +23,10 @@
   z-index: 10;
 }
 .menu {
-  position: absolute;
-  top: 10px;
-  left: 140px;
-  right: 84px;
-  z-index: 10;
+  flex: 1;
+  display: flex;
+  justify-content: center;
+  margin-left: 10Px;
   .menuLine {
     position: absolute;
     left: 0;
@@ -55,23 +61,21 @@
   }
 }
 .tabsContent {
+  width: 100vw;
+  height: 100vh;
   :global {
     .van-tabs__wrap {
       display: none !important;
     }
     .van-tabs__content {
-      width: 100vw;
-      height: 100vh;
-      .van-swipe {
-        width: 100vw;
-        height: 100vh;
-      }
+      width: 100%;
+      height: 100%;
     }
   }
 }
 .videoItem {
-  width: 100vw;
-  height: 100vh;
+  width: 100%;
+  height: 100%;
   --plyr-color-main: var(--van-primary);
   video {
     width: 100%;
@@ -84,13 +88,50 @@
   }
 }
 .imgItem {
-  width: 100vw;
-  height: 100vh;
+  width: 100%;
+  height: 100%;
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+  }
 }
 .songItem {
-  width: 100vw;
-  height: 100vh;
+  width: 100%;
+  height: 100%;
+}
+.fullBtn {
+  position: fixed;
+  top: 50%;
+  right: 40px;
+  width: 38px;
+  height: 55px;
+  transform: translateY(-50%);
+  background: rgba(51, 51, 51, 0.15);
+  border-radius: 8px;
   display: flex;
-  justify-content: center;
+  flex-direction: column;
   align-items: center;
+  color: #fff;
+  justify-content: space-evenly;
+  &:active {
+    opacity: 0.8;
+  }
+}
+.popup {
+  background: transparent;
+}
+.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%);
+  }
 }

+ 190 - 23
src/views/coursewarePlay/index.tsx

@@ -1,5 +1,14 @@
-import { Icon, Swipe, SwipeItem, Tab, Tabs } from 'vant'
-import { defineComponent, onMounted, reactive, nextTick } from 'vue'
+import { Button, Icon, Popup, Swipe, SwipeItem, Tab, Tabs } from 'vant'
+import {
+  defineComponent,
+  onMounted,
+  reactive,
+  nextTick,
+  onUnmounted,
+  ref,
+  watch,
+  Transition
+} from 'vue'
 import iconBack from './image/back.svg'
 import styles from './index.module.less'
 import Plyr from 'plyr'
@@ -7,15 +16,57 @@ import 'plyr/dist/plyr.css'
 import request from '@/helpers/request'
 import { state } from '@/state'
 import { useRoute } from 'vue-router'
+import { postMessage } from '@/helpers/native-message'
+import OHeader from '@/components/o-header'
+import MusicScore from './component/musicScore'
+import iconMenu from './image/icon-menu.svg'
+import Points from './component/points'
 
 export default defineComponent({
   name: 'CoursewarePlay',
   setup() {
+    const handleInit = () => {
+      postMessage({
+        api: 'setRequestedOrientation',
+        content: {
+          orientation: 0
+        }
+      })
+      postMessage({
+        api: 'setBarStatus',
+        content: {
+          status: 0
+        }
+      })
+      postMessage({
+        api: 'setStatusBarVisibility',
+        content: {
+          isVisibility: 0
+        }
+      })
+
+      // window.addEventListener('message', (res: any) => {
+      //   // console.log(res)
+      //   if (res?.data?.api) {
+      //     const { api } = res.data
+      //     if (api === 'touchstart') {
+      //       isTouch.value = true
+      //       console.log('🚀 ~ 父页面touchstart')
+      //     }
+      //     if (api === 'touchend') {
+      //       isTouch.value = false
+      //       console.log('🚀 ~ 父页面touchend')
+      //     }
+      //   }
+      // })
+    }
+
     const route = useRoute()
     const data = reactive({
       detail: null,
       active: '',
-      knowledgePointList: [] as any
+      knowledgePointList: [] as any,
+      showHead: true
     })
     const getDetail = async () => {
       try {
@@ -26,7 +77,10 @@ export default defineComponent({
           data.detail = res.data
         }
         if (Array.isArray(res?.data?.knowledgePointList)) {
-          data.knowledgePointList = res.data.knowledgePointList
+          data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
+            n.index = 0
+            return n
+          })
         }
         console.log('数据加载完成')
       } catch (error) {}
@@ -39,41 +93,129 @@ export default defineComponent({
       console.log(document.querySelectorAll('.player'))
       const player = Plyr.setup('.player', {
         debug: false,
-        ratio: '16:9'
+        ratio: '16:9',
+        controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions']
       })
       console.log('🚀 ~ player', player)
     }
     onMounted(() => {
+      handleInit()
       getDetail()
     })
+    // 返回
     const goback = () => {
       history.go(-1)
     }
+    // 所有的切换
+    const handleChange = () => {
+      console.log('切换了')
+      const iframes = document.querySelectorAll('.musicIframe')
+      Array.from(iframes).map((f: any) => {
+        f.contentWindow.postMessage({ api: 'setPlayState' }, '*')
+      })
+    }
+    onUnmounted(() => {
+      postMessage({
+        api: 'setRequestedOrientation',
+        content: {
+          orientation: 1
+        }
+      })
+      postMessage({
+        api: 'setBarStatus',
+        content: {
+          status: 1
+        }
+      })
+      postMessage({
+        api: 'setStatusBarVisibility',
+        content: {
+          isVisibility: 1
+        }
+      })
+    })
+
+    const popupData = reactive({
+      open: false,
+      active: ''
+    })
+    // 监听切换
+    watch(
+      () => popupData.active,
+      () => {
+        console.log(data.active, '监听切换')
+        const knowledge = data.knowledgePointList.find((n: any) => n.id === data.active)
+        if (knowledge && knowledge?.materialList[knowledge.index]) {
+          // 如果是曲谱,隐藏头部
+          if (knowledge.materialList[knowledge.index]?.type === 'SONG') {
+            console.log(2134)
+            data.showHead = false
+            return
+          }
+        }
+        data.showHead = true
+      }
+    )
     return () => (
       <div class={styles.coursewarePlay}>
-        <div class={styles.backBtn} onClick={() => goback()}>
-          <Icon name={iconBack} />
-          返回
-        </div>
-        <div class={styles.menu}>
-          <Tabs v-model:active={data.active} ellipsis={false}>
-            {{
-              default: () => {
-                return data.knowledgePointList.map((n: any) => {
-                  return <Tab title={n.name} name={n.id}></Tab>
-                })
-              }
-              // 'nav-right': () => <div style={{width: '40%'}} class={styles.menuLine}></div>
-            }}
-          </Tabs>
-        </div>
+        <Transition name='top'>
+          {data.showHead && (
+            <div class={styles.headerContainer}>
+              <div class={styles.backBtn} onClick={() => goback()}>
+                <Icon name={iconBack} />
+                返回
+              </div>
+              <div class={styles.menu}>
+                <Tabs
+                  v-model:active={data.active}
+                  ellipsis={false}
+                  onChange={() => {
+                    const knowledge = data.knowledgePointList.find((_: any) => _.id === data.active)
+                    popupData.active = `${data.active}-${
+                      knowledge?.materialList?.[knowledge.index]?.id
+                    }`
+                    // console.log("🚀 ~ popupData.active", popupData.active)
+                    handleChange()
+                  }}
+                >
+                  {{
+                    default: () => {
+                      return data.knowledgePointList.map((n: any) => {
+                        return <Tab title={n.name} name={n.id}></Tab>
+                      })
+                    }
+                    // 'nav-right': () => <div style={{width: '40%'}} class={styles.menuLine}></div>
+                  }}
+                </Tabs>
+              </div>
+            </div>
+          )}
+        </Transition>
+                  
         <Tabs class={styles.tabsContent} animated lazyRender={false} v-model:active={data.active}>
           {data.knowledgePointList.map((item: any) => {
             return (
               <Tab name={item.id}>
-                <Swipe vertical lazyRender={false}>
+                <Swipe
+                  style={{ height: '100vh' }}
+                  showIndicators={false}
+                  loop={false}
+                  initialSwipe={item.index}
+                  vertical
+                  lazyRender={false}
+                  onChange={(val: any) => {
+                    item.index = val
+                    popupData.active = `${item.id}-${item?.materialList?.[item.index]?.id}`
+                    // console.log("🚀 ~ popupData.active", popupData.active)
+                    handleChange()
+                  }}
+                >
                   {Array.isArray(item?.materialList) &&
                     item.materialList.map((m: any) => {
+                      if (popupData.active === '') {
+                        popupData.active = `${item.id}-${m.id}`
+                        console.log('🚀 ~ popupData', popupData)
+                      }
                       return (
                         <SwipeItem>
                           {m.type === 'VIDEO' ? (
@@ -87,7 +229,9 @@ export default defineComponent({
                               <img src={m.content} />
                             </div>
                           ) : (
-                            <div class={styles.songItem}>曲谱</div>
+                            <div class={styles.songItem}>
+                              <MusicScore music={m} />
+                            </div>
                           )}
                         </SwipeItem>
                       )
@@ -97,6 +241,29 @@ export default defineComponent({
             )
           })}
         </Tabs>
+        <div class={styles.fullBtn} onClick={() => (popupData.open = true)}>
+          <img src={iconMenu} />
+          <span>列表</span>
+        </div>
+        <Popup
+          class={styles.popup}
+          overlayClass={styles.overlayClass}
+          position="left"
+          v-model:show={popupData.open}
+        >
+          <Points
+            data={data.knowledgePointList}
+            active={popupData.active}
+            onHandleSelect={(res: any) => {
+              data.active = res.id
+              const knowledge = data.knowledgePointList.find((n: any) => n.id === res.id)
+              // console.log(res,knowledge)
+              knowledge && (knowledge.index = res.index)
+              popupData.active = res.active
+              popupData.open = false
+            }}
+          />
+        </Popup>
       </div>
     )
   }

+ 2 - 1
src/views/information/information-detail.tsx

@@ -1,4 +1,5 @@
 import request from '@/helpers/request'
+import { state } from '@/state'
 import { defineComponent, onMounted, ref } from 'vue'
 import { useRoute } from 'vue-router'
 import styles from './information-detail.module.less'
@@ -12,7 +13,7 @@ export default defineComponent({
     const getDetails = async () => {
       try {
         const { data } = await request.get(
-          '/api-school/sysNewsInformation/detail/' + route.query.id
+          state.platformApi + '/sysNewsInformation/detail/' + route.query.id
         )
         detail.value = data
       } catch {

+ 39 - 0
src/views/information/notice-detail.tsx

@@ -0,0 +1,39 @@
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { defineComponent, onMounted, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import styles from './information-detail.module.less'
+
+export default defineComponent({
+  name: 'notice-detail',
+  setup() {
+    const route = useRoute()
+    const detail = ref<any>({})
+
+    const getDetails = async () => {
+      try {
+        const { data } = await request.get(
+          state.platformApi + '/sysNotice/detail/' + route.query.id
+        )
+        detail.value = data
+      } catch {
+        //
+      }
+    }
+
+    onMounted(() => {
+      getDetails()
+    })
+    return () => (
+      <div class={styles.detail}>
+        <div class={styles.title}>{detail.value.title}</div>
+        <div class={styles.who}>
+          <span>管乐团</span>
+          {detail.value.createTime}
+        </div>
+
+        <div class={styles.content} v-html={detail.value.content}></div>
+      </div>
+    )
+  }
+})

+ 2 - 1
src/views/lessonCourseware/index.module.less

@@ -16,19 +16,20 @@
     height: 130px;
     border-radius: 8px;
     overflow: hidden;
+    background: rgba(247,203,143,1);
     .cover {
       position: absolute;
       left: 0;
       right: 0;
       top: 0;
       bottom: 0;
-      z-index: -1;
       display: block;
       width: 100%;
       height: 100%;
       object-fit: cover;
     }
     .title {
+      position: relative;
       text-align: center;
       padding: 10px;
       color:#742C00;

+ 14 - 5
src/views/lessonCourseware/index.tsx

@@ -1,10 +1,11 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Empty, Grid, GridItem, Icon } from 'vant'
+import { 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'
 import { useRoute, useRouter } from 'vue-router'
+import OEmpty from '@/components/o-empty'
 export default defineComponent({
   name: 'lessonCourseware',
   setup() {
@@ -26,7 +27,13 @@ export default defineComponent({
     })
     const handleClick = (item: any) => {
       if (route.query.code === 'select') {
-        console.log('这里是选择课件')
+        router.push({
+          path: '/courseList',
+          query: {
+            ...route.query,
+            id: item.id,
+          }
+        })
         return
       }
       router.push({
@@ -36,6 +43,8 @@ export default defineComponent({
         }
       })
     }
+
+    
     return () => (
       <div style={{ paddingTop: '14px' }}>
         <Grid gutter={14} columnNum={3} class={styles.grid}>
@@ -54,17 +63,17 @@ export default defineComponent({
                   <img src={item.coverImg} class={styles.cover} />
                   <div class={styles.title}>{item.name}</div>
                   <div class={styles.num}>共{item.courseNum}课</div>
-                  {item.delFlag && (
+                  {/* {item.delFlag && (
                     <div class={styles.look}>
                       <Icon name={iconLook} /> 未解锁
                     </div>
-                  )}
+                  )} */}
                 </div>
               </GridItem>
             )
           })}
         </Grid>
-        {!data.list.length && <Empty description="空空如也" />}
+        {!data.list.length && <OEmpty tips="没有课件" />}
       </div>
     )
   }

+ 41 - 0
src/views/preview-protocol/index.tsx

@@ -0,0 +1,41 @@
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { defineComponent } from 'vue'
+
+// 预览协议 - 原生实名认证使用
+export default defineComponent({
+  name: 'preview-protocol',
+  data() {
+    return {
+      protocolHTML: '' as any
+    }
+  },
+  async mounted() {
+    try {
+      // 判断是否有协议内容
+      if (!this.protocolHTML) {
+        const { data } = await request.get(
+          state.platformApi + '/open/userContractRecord/queryLatestContractTemplate',
+          {
+            params: {
+              contractType: 'REGISTER'
+            }
+          }
+        )
+        this.protocolHTML = data.contractTemplateContent || ''
+      }
+    } catch {
+      //
+    }
+  },
+  render() {
+    return (
+      <div id="mProtocol">
+        <div
+          style="font-size: 14px;padding: 12px;color: #333;line-height: 1.4;"
+          v-html={this.protocolHTML}
+        ></div>
+      </div>
+    )
+  }
+})