lex-xin 6 月之前
父節點
當前提交
a3e7816d09

+ 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',
           {

+ 16 - 16
src/student/live-class/live-detail.tsx

@@ -130,7 +130,7 @@ export default defineComponent({
 
     /** 埋点 */
     useStatisticTracking({
-      objectType: "GROUP",
+      objectType: "LIVE",
       objectId: this.groupId as any
     })
   },
@@ -211,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

+ 15 - 15
src/student/video-class/video-detail.tsx

@@ -55,7 +55,7 @@ export default defineComponent({
 
     /** 埋点 */
     useStatisticTracking({
-      objectType: "GROUP",
+      objectType: "VIDEO",
       objectId: this.params.groupId as any
     })
   },
@@ -160,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>
+    )
+  }
+})

二進制
src/teacher/statistics/images/icon_video.png


二進制
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>

+ 1 - 1
src/views/music/music-detail/index.tsx

@@ -318,7 +318,7 @@ export default defineComponent({
       if(state.platformType === "STUDENT") {
         /** 埋点 */
         useStatisticTracking({
-          objectType: "GROUP",
+          objectType: "MUSIC",
           objectId: route.query.id as any
         })
       }

+ 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') {