Browse Source

Merge branch 'iteration-20241126' into dev

lex-xin 4 months ago
parent
commit
edb2f6d2af
27 changed files with 1228 additions and 282 deletions
  1. 5 3
      src/components/col-share/index.module.less
  2. 2 0
      src/components/col-share/index.tsx
  3. 8 0
      src/router/routes-teacher.ts
  4. 14 14
      src/student/group-class/group-detail.tsx
  5. 22 15
      src/student/live-class/live-detail.tsx
  6. 60 40
      src/student/teacher-dependent/components/practice.tsx
  7. 8 2
      src/student/teacher-dependent/components/vip.tsx
  8. 21 14
      src/student/video-class/video-detail.tsx
  9. 177 0
      src/teacher/statistics/exercise-detail/exercise-detail.module.less
  10. 313 0
      src/teacher/statistics/exercise-detail/exercise-detail.tsx
  11. 9 8
      src/teacher/statistics/home-statistics-detail/buy-item/index.tsx
  12. 1 1
      src/teacher/statistics/home-statistics-detail/index.module.less
  13. 46 8
      src/teacher/statistics/home-statistics-detail/index.tsx
  14. 7 0
      src/teacher/statistics/home-statistics-detail/list/index.module.less
  15. 15 55
      src/teacher/statistics/home-statistics-detail/list/index.tsx
  16. 9 11
      src/teacher/statistics/home-statistics-detail/teacher-item/index.tsx
  17. 1 1
      src/teacher/statistics/home-statistics/index.module.less
  18. 6 1
      src/teacher/statistics/home-statistics/index.tsx
  19. BIN
      src/teacher/statistics/images/icon_video.png
  20. BIN
      src/teacher/statistics/images/record_bg.png
  21. 23 0
      src/teacher/statistics/practice-statistics-detail/index.module.less
  22. 141 14
      src/teacher/statistics/practice-statistics-detail/index.tsx
  23. 126 0
      src/views/music/list/index.module.less
  24. 177 77
      src/views/music/list/index.tsx
  25. 12 2
      src/views/music/music-detail/index.tsx
  26. 6 5
      src/views/order-detail/index.tsx
  27. 19 11
      src/views/order-detail/orderStatus.ts

+ 5 - 3
src/components/col-share/index.module.less

@@ -270,12 +270,14 @@
   justify-content: space-between;
   padding-top: 12px;
 
-  :global(.van-button) {
-    padding: 8px 46px;
-  }
+  // :global(.van-button) {
+    // font-size: 16px;
+    // padding: 8px 46px;
+  // }
 
   .savePictureBtn {
     border-color: #fff;
+    margin-right: 12px;
   }
 
   &.shareGroupTenantBtn {

+ 2 - 0
src/components/col-share/index.tsx

@@ -270,6 +270,7 @@ export default defineComponent({
                 plain
                 class={styles.savePictureBtn}
                 round
+                block
                 loading={this.saveLoading}
                 loadingText="保存中"
                 onClick={this.onSaveImg}
@@ -279,6 +280,7 @@ export default defineComponent({
               <Button
                 type="primary"
                 round
+                block
                 loading={this.shareLoading}
                 loadingText="分享中"
                 onClick={() => {

+ 8 - 0
src/router/routes-teacher.ts

@@ -419,6 +419,14 @@ export default [
         meta: {
           title: '练习统计'
         }
+      },
+      {
+        path: '/exercise-detail',
+        name: 'exercise-detail',
+        component: () => import('@/teacher/statistics/exercise-detail/exercise-detail'),
+        meta: {
+          title: '练习统计'
+        }
       }
     ]
   },

+ 14 - 14
src/student/group-class/group-detail.tsx

@@ -218,21 +218,21 @@ export default defineComponent({
     },
     async onBuy() {
       try {
-        const live = this.live
+        // const live = this.live
         // 判断是否是0无订单
-        if (live.coursePrice <= 0) {
-          this.initLive()
-          await onSubmitZero(() => {
-            Dialog.alert({
-              message: '领取成功',
-              confirmButtonText: '确定',
-              confirmButtonColor: '#2dc7aa'
-            }).then(() => {
-              this._init()
-            })
-          })
-          return
-        }
+        // if (live.coursePrice <= 0) {
+        //   this.initLive()
+        //   await onSubmitZero(() => {
+        //     Dialog.alert({
+        //       message: '领取成功',
+        //       confirmButtonText: '确定',
+        //       confirmButtonColor: '#2dc7aa'
+        //     }).then(() => {
+        //       this._init()
+        //     })
+        //   })
+        //   return
+        // }
         const res = await request.post(
           '/api-student/userOrder/getPendingOrder',
           {

+ 22 - 15
src/student/live-class/live-detail.tsx

@@ -18,6 +18,7 @@ import { state } from '@/state'
 import { browser } from '@/helpers/utils'
 import { tradeOrder } from '../trade/tradeOrder'
 import TheSticky from '@/components/the-sticky'
+import { useStatisticTracking } from '@/helpers/hooks'
 interface IProps {
   courseTime: string
   coursePlan: string
@@ -126,6 +127,12 @@ export default defineComponent({
     } else {
       this.shareUrl = `${location.origin}/teacher/#/shareLive?recomUserId=${state.user.data?.userId}&groupId=${this.groupId}&userType=${state.platformType}&p=tenant`
     }
+
+    /** 埋点 */
+    useStatisticTracking({
+      objectType: "LIVE",
+      objectId: this.groupId as any
+    })
   },
   methods: {
     async _init() {
@@ -204,21 +211,21 @@ export default defineComponent({
     },
     async onBuy() {
       try {
-        const live = this.live
-        // 判断是否是0无订单
-        if (live.coursePrice <= 0) {
-          this.initLive()
-          await onSubmitZero(() => {
-            Dialog.alert({
-              message: '领取成功',
-              confirmButtonText: '确定',
-              confirmButtonColor: '#2dc7aa'
-            }).then(() => {
-              this._init()
-            })
-          })
-          return
-        }
+        // const live = this.live
+        // // 判断是否是0无订单
+        // if (live.coursePrice <= 0) {
+        //   this.initLive()
+        //   await onSubmitZero(() => {
+        //     Dialog.alert({
+        //       message: '领取成功',
+        //       confirmButtonText: '确定',
+        //       confirmButtonColor: '#2dc7aa'
+        //     }).then(() => {
+        //       this._init()
+        //     })
+        //   })
+        //   return
+        // }
         const res = await request.post(
           '/api-student/userOrder/getPendingOrder',
           {

+ 60 - 40
src/student/teacher-dependent/components/practice.tsx

@@ -22,6 +22,7 @@ import { orderStatus } from '@/views/order-detail/orderStatus'
 import ColResult from '@/components/col-result'
 import { tradeOrder } from '@/student/trade/tradeOrder'
 import Tips from './tips'
+import { useStatisticTracking } from '@/helpers/hooks'
 
 export default defineComponent({
   name: 'practice',
@@ -70,7 +71,7 @@ export default defineComponent({
           }
         }
       )
-      
+
       const result = res.data || []
       if (result.length > 0) {
         const userSubjectId = this.subjectId || state.user.data?.subjectId
@@ -79,7 +80,14 @@ export default defineComponent({
         })
         // 判断是否有跟学生相同的科目,如果没有则默认取第一个
         const tempRes = findItem || result[0]
-        const { subjectName, subjectPrice, courseMinutes, subjectId, id, freeMinutes } = tempRes
+        const {
+          subjectName,
+          subjectPrice,
+          courseMinutes,
+          subjectId,
+          id,
+          freeMinutes
+        } = tempRes
         this.subjectInfo = {
           subjectPrice,
           id,
@@ -108,12 +116,18 @@ export default defineComponent({
             version: state.version
           }
         })
-        this.settingStatus =  resVersion.data.check ? false : true
+        this.settingStatus = resVersion.data.check ? false : true
       } else {
         this.settingStatus = false
       }
 
-      
+      if (this.settingStatus) {
+        /** 埋点 */
+        useStatisticTracking({
+          objectType: 'PRACTICE',
+          objectId: this.teacherId as any
+        })
+      }
       this.loadDataStatus = false
     } catch {
       this.loadDataStatus = false
@@ -183,7 +197,7 @@ export default defineComponent({
         this.calendarList = tempObj
         this.calendarStatus = result.length > 0
       } catch {
-        // 
+        //
       }
     },
     onSelectDay(obj: any) {
@@ -197,7 +211,7 @@ export default defineComponent({
         !isExist && list.push({ ...item })
       })
       // 去掉不在
-      let tempList: any[] = []
+      const tempList: any[] = []
       list.forEach((item: any) => {
         const isExist = result.some(
           (course: any) => course.startTime === item.startTime
@@ -227,7 +241,7 @@ export default defineComponent({
     },
     async _lookCourse(callBack?: Function) {
       try {
-        let times = [] as any
+        const times = [] as any
         this.selectCourseList.forEach((item: any) => {
           times.push({
             startTime: item.startTime,
@@ -390,13 +404,17 @@ export default defineComponent({
   render() {
     return (
       <>
-        <div class={styles.tipSection}><Tips type="PRACTICE" title="什么是趣纠课?" content="趣纠课以一对一专属、高度针对性的形式进行,每次课程时长为25分钟。本课程专为解决学生日常练习中的疑问与误区设计,尤其适合那些在自我练习后感到困惑或不确定自己方法是否正确的学生。不同于传统的教学模式,趣纠课不侧重于新知识或新技能的传授,而是全心全意致力于检查学生现有的练习成果,并及时纠正其中出现的问题。这种方式不仅有助于学生巩固已掌握的知识和技能,还能有效防止错误习惯的形成和发展,为他们今后的学习打下更加坚实的基础。" /></div>
+        <div class={styles.tipSection}>
+          <Tips
+            type="PRACTICE"
+            title="什么是趣纠课?"
+            content="趣纠课以一对一专属、高度针对性的形式进行,每次课程时长为25分钟。本课程专为解决学生日常练习中的疑问与误区设计,尤其适合那些在自我练习后感到困惑或不确定自己方法是否正确的学生。不同于传统的教学模式,趣纠课不侧重于新知识或新技能的传授,而是全心全意致力于检查学生现有的练习成果,并及时纠正其中出现的问题。这种方式不仅有助于学生巩固已掌握的知识和技能,还能有效防止错误习惯的形成和发展,为他们今后的学习打下更加坚实的基础。"
+          />
+        </div>
         {!this.loadDataStatus &&
           (this.settingStatus ? (
             <>
               <div class={styles.practice}>
-                
-
                 <CellGroup class={styles.group} border={false}>
                   <Cell
                     title="选择专业"
@@ -455,36 +473,38 @@ export default defineComponent({
                     />
                   </div>
                 )}
-                {this.showSelectList.length > 0 && <Cell
-                  class={[styles.arrangeCell]}
-                  v-slots={{
-                    title: () => (
-                      <div class={styles.rTitle}>
-                        <span>已选择课程时间</span>
-                      </div>
-                    ),
-                    label: () => (
-                      <div class={styles.rTag}>
-                        {this.showSelectList.map((item: any) => (
-                          <>
-                            <Tag
-                              plain
-                              round
-                              closeable
-                              size="large"
-                              type="primary"
-                              class={styles.tag}
-                              onClose={() => this.onCloseTag(item)}
-                            >
-                              {item.title}
-                            </Tag>
-                            <br />
-                          </>
-                        ))}
-                      </div>
-                    )
-                  }}
-                ></Cell>}
+                {this.showSelectList.length > 0 && (
+                  <Cell
+                    class={[styles.arrangeCell]}
+                    v-slots={{
+                      title: () => (
+                        <div class={styles.rTitle}>
+                          <span>已选择课程时间</span>
+                        </div>
+                      ),
+                      label: () => (
+                        <div class={styles.rTag}>
+                          {this.showSelectList.map((item: any) => (
+                            <>
+                              <Tag
+                                plain
+                                round
+                                closeable
+                                size="large"
+                                type="primary"
+                                class={styles.tag}
+                                onClose={() => this.onCloseTag(item)}
+                              >
+                                {item.title}
+                              </Tag>
+                              <br />
+                            </>
+                          ))}
+                        </div>
+                      )
+                    }}
+                  ></Cell>
+                )}
 
                 <Popup show={this.selectStatus} class={styles.selectPopup}>
                   <div class={styles.selectContainer}>

+ 8 - 2
src/student/teacher-dependent/components/vip.tsx

@@ -23,6 +23,7 @@ import ColResult from '@/components/col-result'
 import { tradeOrder } from '@/student/trade/tradeOrder'
 import icon3 from '../images/icon3.png'
 import Tips from './tips'
+import { useStatisticTracking } from '@/helpers/hooks'
 
 export default defineComponent({
   name: 'VIP_COURSE',
@@ -71,7 +72,6 @@ export default defineComponent({
           }
         }
       )
-      
       const result = res.data || []
       if (result.length > 0) {
         const userSubjectId = this.subjectId || state.user.data?.subjectId
@@ -114,7 +114,13 @@ export default defineComponent({
         this.settingStatus = false
       }
 
-     
+      if(this.settingStatus) {
+        /** 埋点 */
+        useStatisticTracking({
+          objectType: "VIP_COURSE",
+          objectId: this.teacherId as any
+        })
+      }
       this.loadDataStatus = false
     } catch {
       this.loadDataStatus = false

+ 21 - 14
src/student/video-class/video-detail.tsx

@@ -17,6 +17,7 @@ import { setLogin, state } from '@/state'
 import { browser } from '@/helpers/utils'
 import { usePageVisibility } from '@vant/use'
 import TheSticky from '@/components/the-sticky'
+import { useStatisticTracking } from '@/helpers/hooks'
 export default defineComponent({
   name: 'VideoDetail',
   data() {
@@ -51,6 +52,12 @@ export default defineComponent({
     } else {
       this.shareUrl = `${location.origin}/teacher#/shareVideo?recomUserId=${state.user.data?.userId}&groupId=${this.params.groupId}&userType=${state.platformType}&p=tenant`
     }
+
+    /** 埋点 */
+    useStatisticTracking({
+      objectType: "VIDEO",
+      objectId: this.params.groupId as any
+    })
   },
   methods: {
     async _init() {
@@ -153,22 +160,22 @@ export default defineComponent({
           return
         }
 
-        const userInfo = this.userInfo
+        // const userInfo = this.userInfo
 
         // 判断是否是0无订单
-        if (userInfo.lessonPrice <= 0) {
-          this.initVideo()
-          await onSubmitZero(() => {
-            Dialog.alert({
-              message: '领取成功',
-              confirmButtonText: '确定',
-              confirmButtonColor: '#2dc7aa'
-            }).then(() => {
-              this._init()
-            })
-          })
-          return
-        }
+        // if (userInfo.lessonPrice <= 0) {
+        //   this.initVideo()
+        //   await onSubmitZero(() => {
+        //     Dialog.alert({
+        //       message: '领取成功',
+        //       confirmButtonText: '确定',
+        //       confirmButtonColor: '#2dc7aa'
+        //     }).then(() => {
+        //       this._init()
+        //     })
+        //   })
+        //   return
+        // }
 
         const res = await request.post(
           '/api-student/userOrder/getPendingOrder',

+ 177 - 0
src/teacher/statistics/exercise-detail/exercise-detail.module.less

@@ -0,0 +1,177 @@
+.member-record {
+  min-height: 100vh;
+  position: relative;
+  overflow: hidden;
+  .memberHeader {
+    background: #00d1a1;
+  }
+
+  .headerContent {
+    min-height: 158px;
+    margin-bottom: 70px;
+    position: relative;
+
+    .userInfo {
+      position: absolute;
+      bottom: 74px;
+      left: 34px;
+      display: flex;
+      align-items: center;
+      .userImg {
+        width: 24px;
+        height: 24px;
+        border-radius: 50%;
+      }
+      .userName {
+        font-weight: 600;
+        font-size: 16px;
+        color: #ffffff;
+        line-height: 20px;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        max-width: 112px;
+        padding: 0 4px
+      }
+      .subjectName {
+        font-size: 10px;
+        color: #ff8c00;
+        line-height: 14px;
+        background: #fff1de;
+        border-radius: 4px;
+        padding: 1px 4px;
+      }
+    }
+
+    .headerImg {
+      width: 100%;
+      max-height: 158px;
+      padding: 0 14px;
+    }
+
+    .headerCount {
+      position: absolute;
+      bottom: -70px;
+      display: flex;
+      align-items: center;
+      margin: 0 14px;
+      width: calc(100% - 28px);
+      background-color: #fff;
+      border-radius: 12px;
+      height: 100px;
+      .headerItem {
+        position: relative;
+        flex-basis: 33.33%;
+        display: flex;
+        align-items: center;
+        flex-direction: column;
+        font-size: 12px;
+        color: #778098;
+        line-height: 17px;
+        .num {
+          font-size: 16px;
+          font-weight: 500;
+          color: #2dc7aa;
+          line-height: 22px;
+          padding-bottom: 8px;
+        }
+
+        &::after {
+          content: ' ';
+          display: inline-block;
+          position: absolute;
+          right: 0px;
+          top: 15px;
+          width: 1px;
+          height: 16px;
+          background: #ebebeb;
+        }
+
+        &:last-child {
+          &::after {
+            display: none;
+          }
+        }
+      }
+    }
+  }
+
+  .memberCell {
+    margin: 10px 14px 0;
+    width: auto;
+    border-radius: 10px;
+    overflow: hidden;
+    .dataItem {
+      color: #1a1a1a;
+    }
+    :global {
+      .van-cell__value {
+        flex: auto 1;
+      }
+
+      .iconfont-down {
+        margin-left: 4px;
+        transform: scale(0.8);
+      }
+    }
+  }
+}
+
+.rTitle {
+  display: flex;
+  align-items: center;
+  &::before {
+    margin-right: 8px;
+    content: ' ';
+    display: inline-block;
+    width: 4px;
+    height: 14px;
+    background: #2dc7aa;
+    border-radius: 3px;
+  }
+}
+
+.data-content {
+  margin: 10px 14px 0;
+  border-radius: 10px;
+  overflow: hidden;
+  // &:first-child {
+  //   margin-top: 0;
+  // }
+  .dataTitle {
+    display: flex;
+    align-items: center;
+
+    span {
+      padding-right: 8px;
+      max-width: 150px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  :global {
+    .van-row-item {
+      display: flex;
+      align-items: center;
+    }
+    .van-grid-item__content {
+      padding: 3px;
+    }
+    .van-grid-item__content {
+      background-color: transparent;
+      font-size: 14px;
+    }
+    .van-grid-item__icon-wrapper {
+      font-size: 14px;
+      font-weight: 500;
+      color: #000;
+    }
+    .van-grid-item__text {
+      padding-top: 6px;
+      font-size: 12px;
+      color: #808080;
+    }
+  }
+}

+ 313 - 0
src/teacher/statistics/exercise-detail/exercise-detail.tsx

@@ -0,0 +1,313 @@
+import ColHeader from '@/components/col-header'
+import { defineComponent } from 'vue'
+import styles from './exercise-detail.module.less'
+import {
+  Cell,
+  CellGroup,
+  DatetimePicker,
+  Grid,
+  GridItem,
+  Icon,
+  Image,
+  List,
+  Popup
+} from 'vant'
+import { levelMember } from '@/constant'
+import request from '@/helpers/request'
+import { formatterDate } from '@/helpers/utils'
+import dayjs from 'dayjs'
+import ColResult from '@/components/col-result'
+import { postMessage } from '@/helpers/native-message'
+import { useEventTracking } from '@/helpers/hooks'
+import iconVideo from '../images/icon_video.png'
+import recordBg from '../images/record_bg.png'
+import iconStudent from '@/common/images/icon_student.png'
+
+export default defineComponent({
+  name: 'memberRecord',
+  data() {
+    return {
+      userTrainOverView: {
+        trainDays: 0,
+        trainNum: 0,
+        trainTime: 0
+      },
+      timeStatus: false,
+      currentDate: new Date(),
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      params: {
+        page: 1,
+        rows: 20
+      }
+    }
+  },
+  async mounted() {
+    this.getList()
+    useEventTracking('评测记录')
+  },
+  methods: {
+    onSearch() {
+      this.timeStatus = false
+      this.dataShow = true
+      this.loading = false
+      this.finished = false
+      this.list = []
+      this.params.page = 1
+      this.getList()
+    },
+    async getList() {
+      try {
+        const params = this.params
+        const res = await request.get(
+          '/api-teacher/sysMusicRecord/studentTrainData',
+          {
+            params: {
+              ...params,
+              startTime: dayjs(this.currentDate).format('YYYY-MM')
+            }
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 在第一页的时候才处理数据显示
+        if (result.detail.pageNo === 1) {
+          this.userTrainOverView = result.userTrainOverView
+        }
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.detail.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.detail.rows || [])
+        this.finished = result.detail.pageNo >= result.detail.totalPage
+        this.params.page = result.detail.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles['member-record']}>
+        {/* <Sticky position="top" offsetTop={0}> */}
+        <ColHeader
+          class={styles.memberHeader}
+          background="#00d1a1"
+          backIconColor="white"
+          border={false}
+          color="#fff"
+          // isFixed={false}
+          title=' '
+          v-slots={{
+            default: () => (
+              <div class={styles.headerContent}>
+                <Image class={styles.headerImg} src={recordBg} />
+
+                <div class={styles.userInfo}>
+                  <img class={styles.userImg} src={iconStudent} />
+                  <span class={styles.userName}>王曼曼曼曼曼曼</span>
+                  <span class={styles.subjectName}>长笛</span>
+                </div>
+
+                <div class={styles.headerCount}>
+                  <div class={styles.headerItem}>
+                    <span class={styles.num}>
+                      {this.userTrainOverView.trainDays}天
+                    </span>
+                    <span>累计练习天数</span>
+                  </div>
+                  <div class={styles.headerItem}>
+                    <span class={styles.num}>
+                      {this.userTrainOverView.trainTime}分钟
+                    </span>
+                    <span>累计练习时长</span>
+                  </div>
+                  <div class={styles.headerItem}>
+                    <span class={styles.num}>
+                      {this.userTrainOverView.trainNum}次
+                    </span>
+                    <span>累计练习次数</span>
+                  </div>
+                </div>
+              </div>
+              //   <div class={[styles.certHeader, this.headStatus ? styles.certWhite : null]}>
+              //     老师认证
+              //   </div>
+            )
+          }}
+        />
+        {/* </Sticky> */}
+
+        <div style={{ overflow: 'hidden', paddingBottom: '12px' }}>
+          <Cell
+            class={styles.memberCell}
+            v-slots={{
+              title: () => (
+                <div class={styles.rTitle}>
+                  <span>评测记录</span>
+                </div>
+              ),
+              default: () => (
+                <div
+                  class={styles.dataItem}
+                  onClick={() => (this.timeStatus = true)}
+                >
+                  {dayjs(this.currentDate).format('YYYY年MM月')}
+                  <Icon
+                    classPrefix="iconfont"
+                    name="down"
+                    size={14}
+                    color="var(--van-primary)"
+                  />
+                </div>
+              )
+            }}
+          ></Cell>
+
+          <Popup
+            v-model:show={this.timeStatus}
+            position="bottom"
+            round
+            closeOnPopstate
+          >
+            <DatetimePicker
+              type="year-month"
+              v-model={this.currentDate}
+              formatter={formatterDate}
+              onCancel={() => {
+                this.timeStatus = false
+              }}
+              onConfirm={this.onSearch}
+            />
+          </Popup>
+          {this.dataShow ? (
+            <List
+              v-model:loading={this.loading}
+              finished={this.finished}
+              finishedText=" "
+              immediateCheck={false}
+              class={[styles.liveList, 'mb12']}
+              onLoad={this.getList}
+            >
+              {this.list.map((item: any) => (
+                <CellGroup
+                  class={styles['data-content']}
+                  border={false}
+                  onClick={() => {
+                    const behaviorId = +new Date()
+                    // 酷乐秀云教练的部署目录
+                    const musicScorePath = '/klx-music-score/'
+                    //https://dev.colexiu.com/klx-music-score/#/evaluat-report?id=6856&musicRenderType=staff&systemType=student&Authorization=bearer%2043d42470-e968-4efe-a17a-b859a350cbff
+                    postMessage({
+                      api: 'openAccompanyWebView',
+                      content: {
+                        url:
+                          location.origin +
+                          musicScorePath +
+                          '#/' +
+                          'evaluat-report?id=' +
+                          item.id +
+                          '&behaviorId=' +
+                          behaviorId,
+                        orientation: 0,
+                        isHideTitle: true,
+                        statusBarTextColor: false,
+                        isOpenLight: true
+                      }
+                    })
+                    return
+                  }}
+                >
+                  <Cell
+                    center
+                    v-slots={{
+                      title: () => (
+                        <div class={styles.dataTitle}>
+                          <span>{item.sysMusicScoreName}</span>
+                          {item.videoFilePath && (
+                            <Icon name={iconVideo} size={20} />
+                          )}
+                        </div>
+                      ),
+                      default: () => (
+                        <div class={styles.dataItem}>
+                          {dayjs(item.createTime).format('YYYY/MM/DD HH:mm')}
+                        </div>
+                      )
+                    }}
+                  ></Cell>
+                  <Cell
+                    center
+                    style={{ paddingLeft: '5px', paddingRight: '5px' }}
+                    v-slots={{
+                      title: () => (
+                        <Grid border={false} columnNum={5}>
+                          <GridItem
+                            text="评测难度"
+                            v-slots={{
+                              icon: () => (
+                                <span style={{ color: '#000000' }}>
+                                  {levelMember[item.heardLevel]}
+                                </span>
+                              )
+                            }}
+                          ></GridItem>
+                          <GridItem
+                            text="评测分数"
+                            v-slots={{
+                              icon: () => (
+                                <span style={{ color: '#000000' }}>
+                                  {item.score}分
+                                </span>
+                              )
+                            }}
+                          ></GridItem>
+                          <GridItem
+                            text="音准"
+                            v-slots={{
+                              icon: () => (
+                                <span style={{ color: '#01C1B5' }}>
+                                  {item.intonation}分
+                                </span>
+                              )
+                            }}
+                          ></GridItem>
+                          <GridItem
+                            text="节奏"
+                            v-slots={{
+                              icon: () => (
+                                <span style={{ color: '#FF802C' }}>
+                                  {item.cadence}分
+                                </span>
+                              )
+                            }}
+                          ></GridItem>
+                          <GridItem
+                            text="完成度"
+                            v-slots={{
+                              icon: () => (
+                                <span style={{ color: '#F79C00' }}>
+                                  {item.integrity}分
+                                </span>
+                              )
+                            }}
+                          ></GridItem>
+                        </Grid>
+                      )
+                    }}
+                  ></Cell>
+                </CellGroup>
+              ))}
+            </List>
+          ) : (
+            <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无记录" />
+          )}
+        </div>
+      </div>
+    )
+  }
+})

+ 9 - 8
src/teacher/statistics/home-statistics-detail/buy-item/index.tsx

@@ -2,7 +2,8 @@ import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import { Cell, CellGroup } from 'vant'
 import iconTimer from '@/common/images/icon_timer2.png'
-import iconMoney from '../../images/icon-money.png'
+import icon_student from '@/common/images/icon_student.png'
+import { moneyFormat } from '@/helpers/utils'
 
 export default defineComponent({
   name: 'teacher-item',
@@ -19,31 +20,31 @@ export default defineComponent({
   setup(props) {
     return () => (
       <CellGroup border={false}>
-        {props.list.map((item, index) => (
+        {props.list.map((item: any) => (
           <Cell class={styles.cell} center>
             {{
               title: () => (
                 <div class={styles.top}>
                   <div class={styles.timer}>
                     <img src={iconTimer} />
-                    <span>购买时间:2024-10-30 15:23</span>
+                    <span>购买时间:{item.orderTime}</span>
                   </div>
                   <div class={styles.userInfo}>
-                    <span class={styles.name}>张涵宇张涵宇张涵宇</span>
-                    <img src={iconMoney} />
+                    <span class={styles.name}>{item.userName}</span>
+                    <img src={item.userAvatar || icon_student} />
                   </div>
                 </div>
               ),
               label: () => (
                 <div class={styles.content}>
-                  <img class={[styles.cover, props.isSquare && styles.cover1]} />
+                  <img class={[styles.cover, props.isSquare && styles.cover1]} src={item.bizCover} />
                   <div class={styles.info}>
-                    <div class={styles.iTitle}>著名大号大师严琦带你去走近带你去走近带你去走近音</div>
+                    <div class={styles.iTitle}>{item.bizName}</div>
                     <div class={styles.iPrice}>
                       <span>预计收入</span>
                       <span class={styles.price}>
                         <i>¥</i>
-                        560.00
+                        {moneyFormat(item.amount || 0)}
                       </span>
                     </div>
                   </div>

+ 1 - 1
src/teacher/statistics/home-statistics-detail/index.module.less

@@ -25,7 +25,7 @@
       background: #2dc7aa;
       border-radius: 2px;
     }
-    .van-tabs__content {
+    .van-tabs__content, .van-tab__panel {
       height: calc(100vh - var(--van-tabs-line-height) - var(--header-height, 0));
       overflow-x: hidden;
       overflow-y: auto;

+ 46 - 8
src/teacher/statistics/home-statistics-detail/index.tsx

@@ -1,37 +1,75 @@
-import { defineComponent } from 'vue'
+import { defineComponent, reactive } from 'vue'
 import styles from './index.module.less'
 import { Tab, Tabs } from 'vant'
 import ColHeader from '@/components/col-header'
 import TheSticky from '@/components/the-sticky'
 import List from './list'
+import request from '@/helpers/request'
 
 export default defineComponent({
   name: 'HomeStatistics',
   setup() {
+    const typeList = reactive({
+      VIP_COURSE: 0,
+      PRACTICE: 0,
+      GROUP: 0,
+      LIVE: 0,
+      VIDEO: 0,
+      MUSIC: 0
+    })
+    const getSysConfig = async () => {
+      try {
+        const { data } = await request.get('/api-teacher/sysConfig/list', {
+          params: {
+            group: 'ACCOUNT_PERIOD'
+          }
+        })
+        const result = data || []
+        result.forEach((item: any) => {
+          if (item.paramName === 'vip_course_account_period') {
+            typeList.VIP_COURSE = Number(item.paramValue || 0)
+          } else if (item.paramName === 'practice_account_period') {
+            typeList.PRACTICE = Number(item.paramValue || 0)
+          } else if (item.paramName === 'group_course_account_period') {
+            typeList.GROUP = Number(item.paramValue || 0)
+          } else if (item.paramName === 'live_account_period') {
+            typeList.LIVE = Number(item.paramValue || 0)
+          } else if (item.paramName === 'video_account_period') {
+            typeList.VIDEO = Number(item.paramValue || 0)
+          } else if (item.paramName === 'music_account_period') {
+            typeList.MUSIC = Number(item.paramValue || 0)
+          }
+        })
+      } catch {
+        //
+      }
+    }
+
+    getSysConfig()
     return () => (
       <div class={styles.homeStatistics}>
         <TheSticky position="top">
           <ColHeader border={false} background="transparent" />
         </TheSticky>
 
-        <Tabs class={styles.tabs} swipeable>
+        <Tabs class={styles.tabs}>
           <Tab title="VIP定制课" name="VIP_COURSE">
-            <List type="VIP_COURSE" />
+            <List accountPeriod={typeList.VIP_COURSE} type="VIP_COURSE" />
           </Tab>
           <Tab title="趣纠课" name="PRACTICE">
-            <List type="PRACTICE" />
+            <List accountPeriod={typeList.PRACTICE} type="PRACTICE" />
           </Tab>
           <Tab title="小组课" name="GROUP">
-            <List type="GROUP" />
+            <List accountPeriod={typeList.GROUP} type="GROUP" />
           </Tab>
           <Tab title="直播课" name="LIVE">
-            <List type="LIVE" />
+            <List accountPeriod={typeList.LIVE} type="LIVE" />
           </Tab>
           <Tab title="视频课" name="VIDEO">
-            <List type="VIDEO" />
+            <List accountPeriod={typeList.VIDEO} type="VIDEO" />
           </Tab>
           <Tab title="乐谱" name="MUSIC">
-            <List type="MUSIC" />
+            <List accountPeriod={typeList.MUSIC} type="MUSIC" />
           </Tab>
         </Tabs>
       </div>

+ 7 - 0
src/teacher/statistics/home-statistics-detail/list/index.module.less

@@ -7,6 +7,13 @@
   border-radius: 10px;
   margin: 12px 14px 0;
   overflow: hidden;
+  :global {
+    .van-list__loading,
+    .van-list__finished-text,
+    .van-list__error-text {
+      width: 100%;
+    }
+  }
   .incomeTitle {
     display: flex;
     align-items: center;

+ 15 - 55
src/teacher/statistics/home-statistics-detail/list/index.tsx

@@ -1,13 +1,13 @@
-import { defineComponent, PropType, ref } from 'vue'
+import { defineComponent, PropType, reactive, ref } from 'vue'
 import styles from './index.module.less'
-import { CellGroup, List, Sticky } from 'vant'
+import { CellGroup, List } from 'vant'
 import iconMoney from '../../images/icon-money.png'
 import ColResult from '@/components/col-result'
 import Echats from '../echats'
 import TeacherItem from '../teacher-item'
 import BuyItem from '../buy-item'
 import request from '@/helpers/request'
-import { getTimeRange, TIME_TYPE } from '../../home-statistics'
+import { getTimeRange } from '../../home-statistics'
 import { moneyFormat } from '@/helpers/utils'
 
 export default defineComponent({
@@ -18,6 +18,10 @@ export default defineComponent({
         'VIP_COURSE' | 'PRACTICE' | 'GROUP' | 'LIVE' | 'VIDEO' | 'MUSIC'
       >,
       default: 'VIP_COURSE'
+    },
+    accountPeriod: {
+      type: Number,
+      default: 0
     }
   },
   setup(props) {
@@ -31,65 +35,18 @@ export default defineComponent({
     })
 
     const dataShow = ref(true) // 判断是否有数据
-    const state = {
+    const state = reactive({
       statInfo: 0, // 预计课程总收入
-      accountPeriod: 0, // 几天后
       loading: false,
       finished: false,
       params: {
         page: 1,
         rows: 20
       }
-    }
+    })
 
     const tableList = ref<any[]>([])
 
-    const getSysConfig = async () => {
-      try {
-        const { data } = await request.get('/api-teacher/sysConfig/list', {
-          params: {
-            group: 'ACCOUNT_PERIOD'
-          }
-        })
-        const result = data || []
-        result.forEach((item: any) => {
-          if (
-            props.type === 'VIP_COURSE' &&
-            item.paramName === 'vip_course_account_period'
-          ) {
-            state.accountPeriod = item.paramValue
-          } else if (
-            props.type === 'PRACTICE' &&
-            item.paramName === 'practice_account_period'
-          ) {
-            state.accountPeriod = item.paramValue
-          } else if (
-            props.type === 'GROUP' &&
-            item.paramName === 'group_course_account_period'
-          ) {
-            state.accountPeriod = item.paramValue
-          } else if (
-            props.type === 'LIVE' &&
-            item.paramName === 'live_account_period'
-          ) {
-            state.accountPeriod = item.paramValue
-          } else if (
-            props.type === 'VIDEO' &&
-            item.paramName === 'video_account_period'
-          ) {
-            state.accountPeriod = item.paramValue
-          } else if (
-            props.type === 'MUSIC' &&
-            item.paramName === 'music_account_period'
-          ) {
-            state.accountPeriod = item.paramValue
-          }
-        })
-      } catch {
-        //
-      }
-    }
-
     const getDetail = async () => {
       try {
         const { data } = await request.post(
@@ -129,10 +86,12 @@ export default defineComponent({
     }
 
     const getList = async () => {
+      state.loading = true
       try {
         const { data } = await request.post(
           '/api-teacher/home/teacherIncomeList',
           {
+            hideLoading: false,
             data: {
               ...timeRange.value,
               type: props.type,
@@ -141,20 +100,20 @@ export default defineComponent({
           }
         )
 
-        state.loading = false
         state.statInfo = data.statInfo || 0
         tableList.value = tableList.value.concat(data.rows || [])
 
         state.finished = data.pageNo >= data.totalPage
+
         state.params.page = data.pageNo + 1
         dataShow.value = tableList.value.length > 0
       } catch {
         dataShow.value = false
         state.finished = true
       }
+      state.loading = false
     }
 
-    getSysConfig()
     getDetail()
     getList()
 
@@ -186,7 +145,7 @@ export default defineComponent({
           </div>
           {/* </Sticky> */}
           <div class={styles.incomeTip}>
-            实际收入将在课程结束{state.accountPeriod || 0}天后结算
+            实际收入将在课程结束{props.accountPeriod || 0}天后结算
           </div>
 
           <div class={styles.element}></div>
@@ -195,6 +154,7 @@ export default defineComponent({
               v-model:loading={state.loading}
               finished={state.finished}
               finishedText=" "
+              immediateCheck={false}
               onLoad={getList}
             >
               <CellGroup border={false}>

+ 9 - 11
src/teacher/statistics/home-statistics-detail/teacher-item/index.tsx

@@ -2,8 +2,8 @@ import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import { Cell, CellGroup } from 'vant'
 import iconTimer from '@/common/images/icon_timer2.png'
-import iconMoney from '../../images/icon-money.png'
-import ColResult from '@/components/col-result'
+import icon_student from '@/common/images/icon_student.png'
+import { moneyFormat } from '@/helpers/utils'
 
 export default defineComponent({
   name: 'teacher-item',
@@ -16,36 +16,34 @@ export default defineComponent({
   setup(props) {
     return () => (
       <CellGroup border={false}>
-        {props.list.map((item, index) => (
+        {props.list.map((item: any, index) => (
           <Cell class={styles.cell} center>
             {{
               title: () => (
                 <div class={styles.timer}>
                   <img src={iconTimer} />
-                  <span>购买时间:2024-10-30 15:23</span>
+                  <span>购买时间:{item.orderTime}</span>
                 </div>
               ),
               label: () => (
                 <div class={styles.content}>
                   <div class={styles.userInfo}>
-                    <img src={iconMoney} />
+                    <img src={item.userAvatar || icon_student} />
                     <div class={styles.item}>
-                      <span class={styles.name}>
-                        {index === 0 ? '张涵宇张涵宇张涵宇' : '张涵宇'}
-                      </span>
-                      <span class={styles.subjects}>长笛</span>
+                      <span class={styles.name}>{item.userName}</span>
+                      {item.subjectName && <span class={styles.subjects}>{item.subjectName}</span>}
                     </div>
                   </div>
                   <div class={styles.item} style={{ alignItems: 'center' }}>
                     <span class={styles.name}>课时数</span>
                     <span class={styles.classNum}>
-                      12 <i>节</i>
+                      {item.bizQuantity} <i>节</i>
                     </span>
                   </div>
                   <div class={styles.item} style={{ alignItems: 'flex-end' }}>
                     <span class={styles.name}>预计收入</span>
                     <span class={styles.classPrice}>
-                      <i>¥</i> 560.00
+                      <i>¥</i> {moneyFormat(item.amount || 0)}
                     </span>
                   </div>
                 </div>

+ 1 - 1
src/teacher/statistics/home-statistics/index.module.less

@@ -3,7 +3,7 @@
   box-shadow: 0px 2px 10px 0px rgba(229, 229, 229, 0.1);
   border-radius: 10px;
   padding: 12px;
-  margin: 0 14px;
+  // margin: 0 14px;
 }
 
 .homeHead {

+ 6 - 1
src/teacher/statistics/home-statistics/index.tsx

@@ -40,7 +40,7 @@ import { CanvasRenderer } from 'echarts/renderers'
 import { format } from 'path'
 import request from '@/helpers/request'
 import dayjs from 'dayjs'
-import { postMessage } from '@/helpers/native-message'
+import { listenerMessage, postMessage } from '@/helpers/native-message'
 import { browser } from '@/helpers/utils'
 import { useRouter } from 'vue-router'
 
@@ -314,6 +314,11 @@ export default defineComponent({
         router.push({ path: '/home-statistics-detail' })
       }
     }
+
+    // 监听页面返回
+    listenerMessage('webViewOnResume', () => {
+      getDetail()
+    })
     return () => (
       <div class={styles.homeStatistics} ref={homeStatisticsRef}>
         <div class={styles.homeHead}>

BIN
src/teacher/statistics/images/icon_video.png


BIN
src/teacher/statistics/images/record_bg.png


+ 23 - 0
src/teacher/statistics/practice-statistics-detail/index.module.less

@@ -243,6 +243,29 @@
       flex-shrink: 0;
     }
   }
+
+  .filterSection {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .filters {
+    display: flex;
+    align-items: center;
+    flex-direction: column;
+    margin-left: 4px;
+    :global {
+      .iconfont {
+        line-height: 1;
+      }
+    }
+    .upArrow {
+      transform: rotate(180deg) translateY(-1px);
+    }
+    .downArrow {
+      transform: translateY(-3px);
+    }
+  }
 }
 
 .popupContainer {

+ 141 - 14
src/teacher/statistics/practice-statistics-detail/index.tsx

@@ -5,7 +5,7 @@ import iconArrow11 from '../images/icon-arrow1-1.png'
 import icon1 from '../images/icon-1.png'
 import icon2 from '../images/icon-2.png'
 import iconDownload from '../images/icon-download.png'
-import { Button, DatetimePicker, Popup } from 'vant'
+import { Button, DatetimePicker, Icon, Popup } from 'vant'
 import Echats from './echats'
 import ColHeader from '@/components/col-header'
 import TheSticky from '@/components/the-sticky'
@@ -15,6 +15,7 @@ import request from '@/helpers/request'
 import { getTimeRange, TIME_TYPE } from '../home-statistics'
 import ColResult from '@/components/col-result'
 import { promisefiyPostMessage } from '@/helpers/native-message'
+import { useRouter } from 'vue-router'
 
 /** 秒转分 */
 export const formatSecToMin = (second: number) => {
@@ -47,6 +48,7 @@ export const formatSecToHMS = second => {
 export default defineComponent({
   name: 'PracticeDetail',
   setup() {
+    const router = useRouter()
     const searchStatus = ref(false)
     const currentType = ref<TIME_TYPE>('MONTH')
 
@@ -67,7 +69,9 @@ export default defineComponent({
       startTime: new Date(timeRange?.startTime || ''),
       startTimeStr: timeRange?.startTime || '',
       endTime: new Date(timeRange?.endTime || ''),
-      endTimeStr: timeRange?.endTime || ''
+      endTimeStr: timeRange?.endTime || '',
+      sortField: '' as 'totalPracticeTime' | 'averagePracticeTime' | '', // 排序字段
+      sortType: '' as 'ASC' | 'DESC' | '' // 排序方式 ,ASC升序,DESC降序
     })
 
     // 练习统计
@@ -107,7 +111,16 @@ export default defineComponent({
     const onExport = async () => {
       try {
         const { data } = await request.post(
-          '/api-teacher/home/exportStudentPractice'
+          '/api-teacher/home/exportStudentPractice',
+          {
+            data: {
+              startTime: forms.startTimeStr,
+              endTime: forms.endTimeStr,
+              subjectId: forms.subjectId,
+              sortField: forms.sortField,
+              sortType: forms.sortType // 排序方式 ,ASC升序,DESC降序
+            }
+          }
         )
         console.log(data, 'data')
 
@@ -175,10 +188,30 @@ export default defineComponent({
         obj.value.yAxisDataTime = practiceTimeList
         obj.value.xAxisDataCount = xAxisDataCounts
         obj.value.yAxisDataCount = countList
+      } catch {
+        //
+      }
+      forms.loading = false
+    }
 
+    // 用户列表数据
+    const getStudentDetail = async () => {
+      try {
+        const { data } = await request.post(
+          '/api-teacher/home/studentPractice',
+          {
+            data: {
+              startTime: forms.startTimeStr,
+              endTime: forms.endTimeStr,
+              subjectId: forms.subjectId,
+              sortField: forms.sortField,
+              sortType: forms.sortType
+            }
+          }
+        )
         // 学员练习时长
-        const studentPracticeSummary = data.studentPracticeSummary || []
-        let tempStudents: any = []
+        const studentPracticeSummary = data || []
+        const tempStudents: any = []
         studentPracticeSummary.forEach((item: any) => {
           const student = {
             avatar: item.avatar,
@@ -186,17 +219,16 @@ export default defineComponent({
             practiceDays: item.practiceDays || 0,
             studentName: item.studentName,
             subjectName: item.subjectName,
-            totalPracticeTime: formatSecToHMS(item.totalPracticeTime || 0)
+            totalPracticeTime: formatSecToHMS(item.totalPracticeTime || 0),
+            userId: item.userId
           }
-          tempStudents = student
+          tempStudents.push(student)
         })
         obj.value.students = tempStudents
-
         forms.dataShow = tempStudents.length > 0 ? true : false
       } catch {
         //
       }
-      forms.loading = false
     }
 
     const getSubjectList = async () => {
@@ -217,6 +249,7 @@ export default defineComponent({
 
     getSubjectList()
     getDetail()
+    getStudentDetail()
 
     const onChangeTime = (type: TIME_TYPE) => {
       if (searchObj.type === type) return
@@ -245,6 +278,38 @@ export default defineComponent({
       forms.subjectId = searchObj.tempSubjectId
       getDetail()
     }
+
+    /** 排序 */
+    const onSort = (
+      field: 'totalPracticeTime' | 'averagePracticeTime' | ''
+    ) => {
+      console.log(field, 'field')
+      if (!field) return
+
+      if (forms.sortField !== field) {
+        forms.sortType = ''
+      }
+
+      forms.sortField = field
+      if (forms.sortType === 'ASC') {
+        forms.sortType = ''
+      } else if (forms.sortType === 'DESC') {
+        forms.sortType = 'ASC'
+      } else {
+        forms.sortType = 'DESC'
+      }
+      getStudentDetail()
+    }
+
+    /** 跳转详情 */
+    const toDetail = (item: any) => {
+      router.push({
+        path: '/exercise-detail',
+        query: {
+          studentId: item.userId || '',
+        }
+      })
+    }
     return () => (
       <div class={styles.practiceDetail}>
         <TheSticky position="top">
@@ -359,17 +424,79 @@ export default defineComponent({
                       <th class={styles.tdFixedLeft}>学员</th>
                       <th>乐器</th>
                       <th>
-                        <div>练习时长</div>
-                        {/* <div class={styles.filters}>
-                    </div> */}
+                        <div
+                          class={styles.filterSection}
+                          onClick={() => onSort('totalPracticeTime')}
+                        >
+                          练习时长
+                          <div class={styles.filters}>
+                            <Icon
+                              classPrefix="iconfont"
+                              name="down"
+                              class={styles.upArrow}
+                              size={12}
+                              color={
+                                forms.sortField === 'totalPracticeTime' &&
+                                forms.sortType === 'ASC'
+                                  ? 'rgba(223, 128, 16, 1)'
+                                  : 'rgba(0, 0, 0, 0.20)'
+                              }
+                            />
+                            <Icon
+                              classPrefix="iconfont"
+                              name="down"
+                              class={styles.downArrow}
+                              size={12}
+                              color={
+                                forms.sortField === 'totalPracticeTime' &&
+                                forms.sortType === 'DESC'
+                                  ? 'rgba(223, 128, 16, 1)'
+                                  : 'rgba(0, 0, 0, 0.20)'
+                              }
+                            />
+                          </div>
+                        </div>
                       </th>
                       <th>练习天数</th>
-                      <th>平均练习时长</th>
+                      <th>
+                        <div
+                          class={styles.filterSection}
+                          onClick={() => onSort('averagePracticeTime')}
+                        >
+                          平均练习时长
+                          <div class={styles.filters}>
+                            <Icon
+                              classPrefix="iconfont"
+                              name="down"
+                              class={styles.upArrow}
+                              size={12}
+                              color={
+                                forms.sortField === 'averagePracticeTime' &&
+                                forms.sortType === 'ASC'
+                                  ? 'rgba(223, 128, 16, 1)'
+                                  : 'rgba(0, 0, 0, 0.20)'
+                              }
+                            />
+                            <Icon
+                              classPrefix="iconfont"
+                              name="down"
+                              class={styles.downArrow}
+                              size={12}
+                              color={
+                                forms.sortField === 'averagePracticeTime' &&
+                                forms.sortType === 'DESC'
+                                  ? 'rgba(223, 128, 16, 1)'
+                                  : 'rgba(0, 0, 0, 0.20)'
+                              }
+                            />
+                          </div>
+                        </div>
+                      </th>
                     </tr>
                   </thead>
                   <tbody>
                     {obj.value.students.map((item: any) => (
-                      <tr>
+                      <tr onClick={() => toDetail(item)}>
                         <td class={styles.tdFixedLeft}>
                           <img class={styles.userImg} src={item.avatar} />
                           <span>{item.studentName}</span>

+ 126 - 0
src/views/music/list/index.module.less

@@ -55,6 +55,14 @@
     color: #131415;
   }
 
+  &.labelActive {
+    :global {
+      .iconfont-down {
+        transform: rotate(180deg) translateY(1px);
+      }
+    }
+  }
+
   :global {
     .van-list__loading,
     .van-list__finished-text,
@@ -236,3 +244,121 @@
     }
   }
 }
+
+
+
+.popupContainer {
+  // max-height: 504px;
+  // overflow-x: hidden;
+  // overflow-y: auto;
+  .popupTitle {
+    position: sticky;
+    z-index: 1;
+    top: 0;
+    text-align: center;
+    font-weight: 600;
+    font-size: 18px;
+    color: #333333;
+    line-height: 24px;
+    padding: 18px 0 12px;
+  }
+
+  .popupSearchList {
+    min-height: 30vh;
+    max-height: 50vh;
+    overflow: hidden auto;
+  }
+
+  .popupSection {
+    padding: 0 16px 18px;
+    .title {
+      display: flex;
+      justify-content: space-between;
+      padding-bottom: 10px;
+      span {
+        display: flex;
+        align-items: center;
+        font-weight: 600;
+        font-size: 15px;
+        color: #333333;
+        line-height: 18px;
+        &::before {
+          content: '';
+          display: inline-block;
+          width: 3px;
+          height: 12px;
+          background: linear-gradient(180deg, #59e5d4 0%, #2dc7aa 100%);
+          border-radius: 2px;
+          margin-right: 4px;
+        }
+      }
+    }
+
+    .timeCount {
+      display: flex;
+      align-items: center;
+
+      p {
+        margin-left: 10px;
+        flex: 1;
+        background: #f8f8f8;
+        border: 1px solid #f8f8f8;
+        border-radius: 4px;
+        font-size: 13px;
+        color: #999999;
+        line-height: 18px;
+        text-align: center;
+        padding: 6px 0;
+        &:first-child {
+          margin-left: 0;
+        }
+
+        &.active {
+          background: #e9fff8;
+          border-radius: 4px;
+          border: 1px solid #2dc7aa;
+          color: #2dc7aa;
+        }
+      }
+    }
+
+    .timeSubject {
+      flex-wrap: wrap;
+      margin-left: -5px;
+      margin-right: -5px;
+      p {
+        width: calc(25% - 10px);
+        padding: 6px 3px;
+        margin: 0 5px;
+        flex: none;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        margin-bottom: 9px;
+        box-sizing: border-box;
+        &:first-child {
+          margin-left: 5px;
+        }
+
+      }
+    }
+  }
+
+  .popupBottom {
+    position: sticky;
+    z-index: 1;
+    bottom: 0;
+    border-top: 1px solid #f2f2f2;
+    padding: 20px 13px 30px;
+    display: flex;
+    align-items: center;
+    :global {
+      .van-button {
+        font-size: 16px;
+      }
+      .van-button + .van-button {
+        margin-left: 15px;
+      }
+    }
+  }
+}

+ 177 - 77
src/views/music/list/index.tsx

@@ -7,7 +7,7 @@ import {
   ref,
   watch
 } from 'vue'
-import { Sticky, List, Popup, Icon, Switch, Tabs, Tab } from 'vant'
+import { Sticky, List, Popup, Icon, Switch, Tabs, Tab, Button } from 'vant'
 import Search from '@/components/col-search'
 import request from '@/helpers/request'
 // import Item from './item'
@@ -27,6 +27,7 @@ import bgImg from './icons/bgImg.png'
 import iconSearch from './icons/icon_search.png'
 import { browser } from '@/helpers/utils'
 import TheSticky from '@/components/the-sticky'
+import deepClone from '@/helpers/deep-clone'
 
 const noop = () => {
   //
@@ -87,6 +88,12 @@ export default defineComponent({
 
     const stickyHeight = ref(height || 0)
 
+    const searchObj = reactive({
+      searchStatus: false,
+      tagId: '',
+      chargeType: ''
+    })
+
     const updateStickyHeight = (height: number) => {
       stickyHeight.value = height
     }
@@ -163,9 +170,10 @@ export default defineComponent({
 
     const params = reactive({
       search: (route.query.search as string) || '',
-      musicSortType: (route.query.type as string) || "",
+      musicSortType: (route.query.type as string) || '',
       // exquisiteFlag: 1,
-      musicTagIds: route.query.tagids || '',
+      musicTagIds: route.query.tagids || '', // 标签
+      changeType: '', // 曲目类型
       page: 1,
       ...defauleParams,
       ...tempParams
@@ -227,17 +235,17 @@ export default defineComponent({
     }
 
     // 设置默认声部
-    const setDefaultSubject = async (subjectId: any) => {
-      try {
-        await request.post('/api-teacher/teacher/defaultSubject', {
-          params: {
-            subjectId
-          }
-        })
-      } catch {
-        //
-      }
-    }
+    // const setDefaultSubject = async (subjectId: any) => {
+    //   try {
+    //     await request.post('/api-teacher/teacher/defaultSubject', {
+    //       params: {
+    //         subjectId
+    //       }
+    //     })
+    //   } catch {
+    //     //
+    //   }
+    // }
 
     const onComfirm = tags => {
       const tempTags: any = {}
@@ -336,7 +344,7 @@ export default defineComponent({
     })
 
     return () => {
-      // const tagList = ((state.value && state.value.data) as any) || []
+      const tagList = ((state.value && state.value.data) as any) || []
       return (
         <>
           {!hideSearch && (
@@ -367,7 +375,7 @@ export default defineComponent({
                           class={styles.tabSection}
                           v-model:active={params.musicSortType}
                           shrink
-                          onClick-tab={(val) => {
+                          onClick-tab={val => {
                             params.musicSortType = val.name
                             onSearch(params.search)
                           }}
@@ -379,29 +387,6 @@ export default defineComponent({
                         </Tabs>
                       </div>
                     )
-                    // right: () =>
-                    //   !isAudit.value && (
-                    //     <span
-                    //       class={styles.fleg}
-                    //       onClick={() => {
-                    //         // 不要看这个字段的意思
-                    //         exquisiteFlag.value != exquisiteFlag.value
-                    //         useSubjectId(
-                    //           SubjectEnum.MUSIC_FREE,
-                    //           JSON.stringify({
-                    //             chargeType: exquisiteFlag.value
-                    //           }),
-                    //           'set'
-                    //         )
-                    //         data.value = null
-                    //         params.page = 1
-                    //         FetchList()
-                    //       }}
-                    //     >
-                    //       <Switch v-model={exquisiteFlag.value} size="20px" />
-                    //       <span>免费</span>
-                    //     </span>
-                    //   )
                   }}
                 />
                 <Search
@@ -409,25 +394,25 @@ export default defineComponent({
                   background="transparent"
                   inputBackground="transparent"
                   leftIcon={iconSearch}
-                  // v-slots={{
-                  //   left: () => (
-                  //     <div
-                  //       class={styles.label}
-                  //       onClick={() => (subject.show = true)}
-                  //     >
-                  //       {baseState.platformType === 'TEACHER'
-                  //         ? teacherDetaultSubject.value.name
-                  //         : subject.name}
-
-                  //       <Icon
-                  //         classPrefix="iconfont"
-                  //         name="down"
-                  //         size={12}
-                  //         color="#fff"
-                  //       />
-                  //     </div>
-                  //   )
-                  // }}
+                  v-slots={{
+                    left: () => (
+                      <div
+                        class={[
+                          styles.label,
+                          searchObj.searchStatus ? styles.labelActive : ''
+                        ]}
+                        onClick={() => (searchObj.searchStatus = true)}
+                      >
+                        筛选
+                        <Icon
+                          classPrefix="iconfont"
+                          name="down"
+                          size={12}
+                          color="#fff"
+                        />
+                      </div>
+                    )
+                  }}
                 />
                 {/* <Tabs
                   shrink
@@ -457,25 +442,26 @@ export default defineComponent({
                 inputBackground="white"
                 // leftIcon={iconSearch}
                 class={styles.searchGroup}
-                // v-slots={{
-                //   left: () => (
-                //     <div
-                //       class={[styles.label, styles.searchs]}
-                //       onClick={() => (subject.show = true)}
-                //     >
-                //       {baseState.platformType === 'TEACHER'
-                //         ? teacherDetaultSubject.value.name
-                //         : subject.name}
-
-                //       <Icon
-                //         classPrefix="iconfont"
-                //         name="down"
-                //         size={12}
-                //         color="#949597"
-                //       />
-                //     </div>
-                //   )
-                // }}
+                v-slots={{
+                  left: () => (
+                    <div
+                        class={[
+                          styles.label,
+                          styles.searchs,
+                          searchObj.searchStatus ? styles.labelActive : ''
+                        ]}
+                        onClick={() => (searchObj.searchStatus = true)}
+                      >
+                        筛选
+                        <Icon
+                          classPrefix="iconfont"
+                          name="down"
+                          size={12}
+                          color="#333"
+                        />
+                      </div>
+                  )
+                }}
               />
             </Sticky>
           ) : (
@@ -565,6 +551,120 @@ export default defineComponent({
               onComfirm={onComfirmSubject}
             />
           </Popup> */}
+
+          <Popup
+            round
+            closeable
+            safe-area-inset-bottom
+            position="bottom"
+            v-model:show={searchObj.searchStatus}
+          >
+            <div class={styles.popupContainer}>
+              <div class={styles.popupTitle}>筛选</div>
+
+              <div class={styles.popupSearchList}>
+                <div class={styles.popupSection}>
+                  <div class={styles.title}>
+                    <span>标签</span>
+                  </div>
+
+                  <div class={[styles.timeCount, styles.timeSubject]}>
+                    <p
+                      class={searchObj.tagId === '' ? styles.active : ''}
+                      onClick={() => (searchObj.tagId = '')}
+                    >
+                      全部
+                    </p>
+                    {tagList.map((item: any) => (
+                      <p
+                        class={searchObj.tagId === item.id ? styles.active : ''}
+                        onClick={() => {
+                          searchObj.tagId = item.id
+                        }}
+                      >
+                        {item.name}
+                      </p>
+                    ))}
+                  </div>
+                </div>
+
+                <div class={styles.popupSection}>
+                  <div class={styles.title}>
+                    <span>类型</span>
+                  </div>
+
+                  <div class={[styles.timeCount, styles.timeSubject]}>
+                    <p
+                      class={searchObj.chargeType === '' ? styles.active : ''}
+                      onClick={() => (searchObj.chargeType = '')}
+                    >
+                      全部曲目
+                    </p>
+
+                    <p
+                      class={
+                        searchObj.chargeType === 'VIP' ? styles.active : ''
+                      }
+                      onClick={() => {
+                        searchObj.chargeType = 'VIP'
+                      }}
+                    >
+                      会员曲目
+                    </p>
+                    <p
+                      class={
+                        searchObj.chargeType === 'CHARGE' ? styles.active : ''
+                      }
+                      onClick={() => {
+                        searchObj.chargeType = 'CHARGE'
+                      }}
+                    >
+                      点播曲目
+                    </p>
+                    <p
+                      class={
+                        searchObj.chargeType === 'FREE' ? styles.active : ''
+                      }
+                      onClick={() => {
+                        searchObj.chargeType = 'FREE'
+                      }}
+                    >
+                      免费曲目
+                    </p>
+                  </div>
+                </div>
+              </div>
+
+              <div class={styles.popupBottom}>
+                <Button
+                  round
+                  block
+                  type="default"
+                  onClick={() => {
+                    searchObj.tagId = JSON.parse(JSON.stringify(params.musicTagIds))
+                    searchObj.chargeType = JSON.parse(JSON.stringify(params.changeType))
+                  }}
+                >
+                  重置
+                </Button>
+                <Button
+                  round
+                  block
+                  type="primary"
+                  onClick={() => {
+                    params.musicTagIds = JSON.parse(JSON.stringify(searchObj.tagId))
+                    params.changeType = JSON.parse(JSON.stringify(searchObj.chargeType))
+                    data.value = null
+                    params.page = 1
+                    FetchList()
+                    searchObj.searchStatus = false
+                  }}
+                >
+                  确定
+                </Button>
+              </div>
+            </div>
+          </Popup>
         </>
       )
     }

+ 12 - 2
src/views/music/music-detail/index.tsx

@@ -59,6 +59,7 @@ import Download from './download'
 import { getInstrumentName } from '@/constant/instruments'
 import { svgtopng } from '@/tenant/music/music-detail/formatSvgToImg'
 import { useThrottleFn } from '@vueuse/core'
+import { useStatisticTracking } from '@/helpers/hooks'
 // import {
 //   formatXML,
 //   getCustomInfo,
@@ -301,18 +302,27 @@ export default defineComponent({
           staffData.instrumentId = instrumentId
         }
       }
-      
+
       const subjectIds = state.user.data?.subjectId
       if(subjectIds) {
         const subjectId = subjectIds.split(',')[0]
         staffData.subjectId = subjectId
       }
-        
+
       postMessage({
         api: 'setStatusBarTextColor',
         content: { statusBarTextColor: true }
       })
       await FetchList()
+
+      if(state.platformType === "STUDENT") {
+        /** 埋点 */
+        useStatisticTracking({
+          objectType: "MUSIC",
+          objectId: route.query.id as any
+        })
+      }
+
       const { height } = useRect(headers as any)
       const footer = useRect(footers as any)
       heightInfo.value = height + footer.height

+ 6 - 5
src/views/order-detail/index.tsx

@@ -185,12 +185,13 @@ export default defineComponent({
     this.orderPrice = orderStatus.orderObject.actualPrice || 0
 
     this.disabledCoupon = orderStatus.orderObject.orderNo ? true : false
-    this.dataLoading = false
+
     // 0元支付特别处理
-    if (this.orderPrice === 0 && orderStatus.orderObject.orderType) {
-      this.loading = true
-      this.onSubmit()
-    }
+    // if (this.orderPrice === 0 && orderStatus.orderObject.orderType) {
+    //   this.loading = true
+    //   this.onSubmit()
+    // } 
+    this.dataLoading = false
 
     this.$nextTick(() => {
       // paymentButton

+ 19 - 11
src/views/order-detail/orderStatus.ts

@@ -116,7 +116,10 @@ export const orderInfos = () => {
       params.bizContent = {
         groupId: item.courseGroupId
       }
-    } else if (item.orderType === 'PRACTICE' || item.orderType === "VIP_COURSE") {
+    } else if (
+      item.orderType === 'PRACTICE' ||
+      item.orderType === 'VIP_COURSE'
+    ) {
       const tempTime = item.classTime || []
       const classCourse: any = []
       tempTime.forEach((time: any) => {
@@ -175,16 +178,16 @@ export const orderInfos = () => {
   })
 }
 
-// 
+//
 export const orderTenantInfos = (otherGoods: any[] = []) => {
   // 商品列表
-  let orderList: any[] =  []
-  if(otherGoods && otherGoods.length > 0) {
+  let orderList: any[] = []
+  if (otherGoods && otherGoods.length > 0) {
     orderList = otherGoods || []
   } else {
     orderList = orderStatus.orderObject.orderList || []
   }
-  
+
   const activityList: any[] = [] // 活动赠品
   const infos = orderList.map((item: any) => {
     const params: any = {
@@ -247,7 +250,10 @@ export const orderTenantInfos = (otherGoods: any[] = []) => {
         videoLessonGroupId: item.courseGroupId,
         payMoney: item.coursePrice || 0
       }
-    } else if (item.orderType === 'PRACTICE' || item.orderType === 'VIP_COURSE') {
+    } else if (
+      item.orderType === 'PRACTICE' ||
+      item.orderType === 'VIP_COURSE'
+    ) {
       const tempTime = item.classTime || []
       const classCourse: any = []
       tempTime.forEach((time: any) => {
@@ -274,7 +280,7 @@ export const orderTenantInfos = (otherGoods: any[] = []) => {
       params.bizContent = {
         activityId: item.activityId
       }
-    } else if(item.orderType == 'DISCOUNT') {
+    } else if (item.orderType == 'DISCOUNT') {
       params.bizContent = item.id
       params.goodsNum = item.num
       params.goodNum = item.num
@@ -295,19 +301,21 @@ export const onSubmitZero = async (callBack?: Function): Promise<void> => {
     const orderObject = orderStatus.orderObject
     const url =
       state.platformType === 'TEACHER'
-        ? '/api-teacher/userOrder/executeOrder'
-        : '/api-student/userOrder/executeOrder'
+        ? '/api-teacher/userOrder/executeOrder/v2'
+        : '/api-student/userOrder/executeOrder/v2'
     const res = await request.post(url, {
       data: {
         orderName: orderObject.orderName,
         orderDesc: orderObject.orderDesc,
         orderType: orderObject.orderType,
-        actualPrice: orderObject.actualPrice || 0,
+        // actualPrice: orderObject.actualPrice || 0,
+        paymentCashAmount: orderObject.actualPrice || 0,
         recomUserId: orderObject.recomUserId,
         activityId: orderObject.activityId,
-        orderInfos: [...orderInfos()]
+        goodsInfos: [...orderInfos()]
       }
     })
+
     const result = res.data || {}
     // 支付成功
     if (result.status == 'PAID') {