瀏覽代碼

Merge remote-tracking branch 'origin/hqyDev' into feature-new

TIANYONG 4 月之前
父節點
當前提交
e8ddfd9bf7
共有 100 個文件被更改,包括 5468 次插入646 次删除
  1. 二進制
      src/common/images/icon_discount.png
  2. 二進制
      src/common/images/icon_studycard.png
  3. 2 0
      src/components/col-protocol/index.tsx
  4. 6 2
      src/constant/index.ts
  5. 25 1
      src/router/routes-student.ts
  6. 18 1
      src/router/routes-teacher.ts
  7. 5 7
      src/shims-vue.d.ts
  8. 二進制
      src/student/activePage/double12Active/imgs/boxImg.png
  9. 二進制
      src/student/activePage/double12Active/imgs/class.png
  10. 二進制
      src/student/activePage/double12Active/imgs/head.png
  11. 二進制
      src/student/activePage/double12Active/imgs/shadow.png
  12. 二進制
      src/student/activePage/double12Active/imgs/subBtn.png
  13. 二進制
      src/student/activePage/double12Active/imgs/svipCon1.png
  14. 二進制
      src/student/activePage/double12Active/imgs/svipCon2.png
  15. 二進制
      src/student/activePage/double12Active/imgs/tip1Img.png
  16. 二進制
      src/student/activePage/double12Active/imgs/tip2Img.png
  17. 340 0
      src/student/activePage/double12Active/index.module.less
  18. 375 0
      src/student/activePage/double12Active/index.tsx
  19. 二進制
      src/student/discount-card/images/bg.png
  20. 二進制
      src/student/discount-card/images/btn-bg.png
  21. 二進制
      src/student/discount-card/images/card-bg.png
  22. 二進制
      src/student/discount-card/images/icon-star.png
  23. 二進制
      src/student/discount-card/images/icon-svip-disabled.png
  24. 二進制
      src/student/discount-card/images/icon-svip.png
  25. 二進制
      src/student/discount-card/images/icon-vip-disabled.png
  26. 二進制
      src/student/discount-card/images/icon-vip.png
  27. 二進制
      src/student/discount-card/images/memo.png
  28. 二進制
      src/student/discount-card/images/ring.png
  29. 二進制
      src/student/discount-card/images/tip.png
  30. 二進制
      src/student/discount-card/images/title1.png
  31. 294 0
      src/student/discount-card/index.module.less
  32. 267 0
      src/student/discount-card/index.tsx
  33. 57 31
      src/student/live-class/live-detail.tsx
  34. 1 1
      src/student/practice-class/index.tsx
  35. 1 1
      src/student/practice-class/model/all-search.tsx
  36. 74 52
      src/student/teacher-dependent/components/live.module.less
  37. 61 57
      src/student/teacher-dependent/components/live.tsx
  38. 8 5
      src/student/teacher-dependent/components/music.tsx
  39. 13 2
      src/student/teacher-dependent/components/practice.module.less
  40. 36 22
      src/student/teacher-dependent/components/practice.tsx
  41. 45 0
      src/student/teacher-dependent/components/tips/index.module.less
  42. 79 0
      src/student/teacher-dependent/components/tips/index.tsx
  43. 86 0
      src/student/teacher-dependent/components/video-item/index.module.less
  44. 86 0
      src/student/teacher-dependent/components/video-item/index.tsx
  45. 4 0
      src/student/teacher-dependent/components/video.module.less
  46. 4 2
      src/student/teacher-dependent/components/video.tsx
  47. 168 0
      src/student/teacher-dependent/components/vip.module.less
  48. 588 0
      src/student/teacher-dependent/components/vip.tsx
  49. 二進制
      src/student/teacher-dependent/images/icon-add.png
  50. 二進制
      src/student/teacher-dependent/images/icon-cert.png
  51. 二進制
      src/student/teacher-dependent/images/icon-live.png
  52. 二進制
      src/student/teacher-dependent/images/icon-message.png
  53. 二進制
      src/student/teacher-dependent/images/icon-small-live.png
  54. 二進制
      src/student/teacher-dependent/images/icon1.png
  55. 二進制
      src/student/teacher-dependent/images/icon2.png
  56. 二進制
      src/student/teacher-dependent/images/icon3.png
  57. 二進制
      src/student/teacher-dependent/images/icon_subject1.png
  58. 二進制
      src/student/teacher-dependent/images/icon_video.png
  59. 二進制
      src/student/teacher-dependent/model/fans-list/images/fans-bg.png
  60. 二進制
      src/student/teacher-dependent/model/fans-list/images/icon-close.png
  61. 80 0
      src/student/teacher-dependent/model/fans-list/index.module.less
  62. 84 0
      src/student/teacher-dependent/model/fans-list/index.tsx
  63. 77 43
      src/student/teacher-dependent/model/teacher-header.module.less
  64. 80 122
      src/student/teacher-dependent/model/teacher-header.tsx
  65. 398 0
      src/student/teacher-dependent/teacher-follow.module.less
  66. 7 8
      src/student/teacher-dependent/teacher-follow.tsx
  67. 79 5
      src/student/teacher-dependent/teacher-home.module.less
  68. 129 26
      src/student/teacher-dependent/teacher-home.tsx
  69. 217 0
      src/student/teacher-dependent/teacher-style/index.module.less
  70. 149 0
      src/student/teacher-dependent/teacher-style/index.tsx
  71. 167 19
      src/student/trade/tradeOrder.ts
  72. 21 24
      src/student/video-class/video-detail.tsx
  73. 二進制
      src/styles/font/DIN_Alternate_Bold.ttf
  74. 4 0
      src/styles/font/index.less
  75. 1 0
      src/styles/index.less
  76. 1 1
      src/teacher/extend-plan/index.tsx
  77. 2 2
      src/teacher/income-consus/echarts.ts
  78. 2 2
      src/teacher/income-consus/index.tsx
  79. 1 1
      src/teacher/layout/auth.tsx
  80. 二進制
      src/teacher/layout/image-cert/bg.png
  81. 二進制
      src/teacher/layout/image-cert/top.png
  82. 99 0
      src/teacher/layout/login-cert.module.less
  83. 244 0
      src/teacher/layout/login-cert.tsx
  84. 2 0
      src/teacher/live-class/create-components/arrange.tsx
  85. 2 0
      src/teacher/piano-room/class-arrangement/create-class/index.tsx
  86. 1 1
      src/teacher/piano-room/tradeOrder.ts
  87. 二進制
      src/teacher/practice-class/icon-question.png
  88. 1 1
      src/teacher/practice-class/model/timer.tsx
  89. 38 0
      src/teacher/practice-class/practice-setting.module.less
  90. 98 170
      src/teacher/practice-class/practice-setting.tsx
  91. 140 0
      src/teacher/practice-class/timer/timer.module.less
  92. 310 0
      src/teacher/practice-class/timer/timer.tsx
  93. 2 2
      src/tenant/exercise-record/echats.ts
  94. 1 1
      src/tenant/trade/tradeOrder.ts
  95. 1 0
      src/views/adapay/payment/index.tsx
  96. 56 3
      src/views/member-center/index.module.less
  97. 162 28
      src/views/member-center/index.tsx
  98. 19 0
      src/views/music/list/index.module.less
  99. 46 3
      src/views/music/list/index.tsx
  100. 104 0
      src/views/order-detail/add-discount/index.module.less

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


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


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

@@ -27,6 +27,7 @@ export default defineComponent({
       default: 'BUY_ORDER'
     }
   },
+  emits: ['protocolExists', 'update:modelValue'],
   data() {
     return {
       exists: true,
@@ -51,6 +52,7 @@ export default defineComponent({
       // console.log(res)
       this.exists = res.data
       this.checked = this.checked || this.exists
+      this.$emit('protocolExists', res.data)
       this.$emit('update:modelValue', this.checked || this.exists)
     } catch {}
     this.checked = this.modelValue

+ 6 - 2
src/constant/index.ts

@@ -1,6 +1,6 @@
 export const goodsType = {
   LIVE: '直播课',
-  PRACTICE: '陪练课',
+  PRACTICE: '趣纠课',
   VIDEO: '视频课',
   VIP: '开通VIP会员',
   SVIP: '开通SVIP会员',
@@ -47,6 +47,10 @@ export const memberSimpleType = {
   PERPETUAL: '永久'
 }
 
+export const studyCardType = {
+  YEAR: '一年期'
+}
+
 export const courseType = {
   NOT_START: '未开始',
   ING: '进行中',
@@ -55,7 +59,7 @@ export const courseType = {
 }
 
 export const bizStatus = {
-  PRACTICE: '陪练课',
+  PRACTICE: '趣纠课',
   LIVE: '直播课',
   VIDEO: '视频课',
   MUSIC: '乐谱',

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

@@ -45,7 +45,7 @@ export default [
         name: 'practiceClass',
         component: () => import('@/student/practice-class/index'),
         meta: {
-          title: '陪练课'
+          title: '趣纠课'
         }
       },
       {
@@ -121,6 +121,14 @@ export default [
         }
       },
       {
+        path: '/teacher-style',
+        name: 'teacher-style',
+        component: () => import('@/student/teacher-dependent/teacher-style'),
+        meta: {
+          title: '个人风采'
+        }
+      },
+      {
         path: '/music-upload',
         component: () => import('@/teacher/music/upload'),
         meta: {
@@ -151,6 +159,22 @@ export default [
         meta: {
           title: '评测曲目'
         }
+      },
+      {
+        path: '/discount-card',
+        component: () =>
+          import('@/student/discount-card/index'),
+        meta: {
+          title: '畅学卡'
+        }
+      },
+      {
+        path: '/double12Active',
+        component: () =>
+          import('@/student/activePage/double12Active'),
+        meta: {
+          title: '双十二限时特惠'
+        }
       }
     ]
   },

+ 18 - 1
src/router/routes-teacher.ts

@@ -54,6 +54,15 @@ const noLoginRouter = [
     meta: {
       title: '分享曲谱'
     }
+  },
+  {
+    path: '/login-cert',
+    name: 'login-cert',
+    component: () => import('@/teacher/layout/login-cert'),
+    meta: {
+      isRegister: false,
+      title: '老师入驻'
+    } as metaType
   }
 ]
 
@@ -189,7 +198,15 @@ export default [
         name: 'practiceSetting',
         component: () => import('@/teacher/practice-class/practice-setting'),
         meta: {
-          title: '陪练课设置'
+          title: '趣纠课设置'
+        }
+      },
+      {
+        path: '/practiceSettingTimer',
+        name: 'practiceSettingTimer',
+        component: () => import('@/teacher/practice-class/timer/timer'),
+        meta: {
+          title: '设置约课时间段'
         }
       },
       {

+ 5 - 7
src/shims-vue.d.ts

@@ -1,14 +1,12 @@
-import dayjs from "dayjs";
-
+import dayjs from 'dayjs'
 
 declare module 'vue-cropper'
 declare module '*.vue' {
-  import { DefineComponent } from 'vue';
-  const component: DefineComponent<{}, {}, any>;
-  export default component;
+  import { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
 }
 
-
 declare module '@vue/runtime-core' {
   export interface ComponentCustomProperties {
     $dayjs: dayjs.Dayjs
@@ -22,4 +20,4 @@ declare module 'vue' {
     '--creationHeight'?: string;
     '--staffBoxHeight'?: string;
   }
-}
+}

二進制
src/student/activePage/double12Active/imgs/boxImg.png


二進制
src/student/activePage/double12Active/imgs/class.png


二進制
src/student/activePage/double12Active/imgs/head.png


二進制
src/student/activePage/double12Active/imgs/shadow.png


二進制
src/student/activePage/double12Active/imgs/subBtn.png


二進制
src/student/activePage/double12Active/imgs/svipCon1.png


二進制
src/student/activePage/double12Active/imgs/svipCon2.png


二進制
src/student/activePage/double12Active/imgs/tip1Img.png


二進制
src/student/activePage/double12Active/imgs/tip2Img.png


+ 340 - 0
src/student/activePage/double12Active/index.module.less

@@ -0,0 +1,340 @@
+.double12Active {
+  width: 100%;
+  background: linear-gradient(180deg, #fdccbf 0%, #fde0cf 100%);
+  padding: 0 16px 108px;
+  box-sizing: border-box;
+  :global {
+    .van-nav-bar .van-nav-bar__left {
+      color: rgb(44, 62, 80);
+    }
+  }
+  .headImg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 369px;
+    z-index: 1;
+    background: url('./imgs//head.png') no-repeat;
+    background-size: 100% 100%;
+  }
+  .activeArea1 {
+    margin-top: calc(
+      369px - var(--van-nav-bar-height) - var(--navBarHeight) - 18px
+    );
+    background-color: #fff;
+    width: 100%;
+    border-radius: 20px;
+    border: 2px solid #ffffff;
+    filter: blur(0px);
+    position: relative;
+    padding: 12px 0px 24px 20px;
+    box-sizing: border-box;
+    .tip1 {
+      position: absolute;
+      left: 2px;
+      top: -24px;
+      width: 187px;
+      height: 43px;
+      z-index: 1;
+    }
+    .shadowImg {
+      position: absolute;
+      left: 0;
+      top: 0;
+      z-index: 0;
+      width: 100%;
+      height: 60px;
+      border-radius: 19px 19px 0 0;
+    }
+    .activeCon {
+      margin-top: 24px;
+      position: relative;
+      z-index: 2;
+      .activeBox {
+        width: 80px;
+        height: 27px;
+        background: #ffdd71;
+        border-radius: 14px;
+        font-family: Helvetica Neue, Helvetica, PingFangSC;
+        font-weight: 600;
+        font-size: 15px;
+        color: #000000;
+        line-height: 27px;
+        text-align: center;
+      }
+      .activeTimes {
+        margin-top: 12px;
+        font-weight: 500;
+        font-size: 15px;
+        color: #333333;
+        line-height: 21px;
+      }
+      .activeListCon {
+        box-sizing: border-box;
+        padding-left: 13px;
+        margin-top: 12px;
+        .activeList {
+          position: relative;
+          font-weight: 500;
+          font-size: 15px;
+          color: #333333;
+          line-height: 25px;
+          margin-top: 3px;
+          &:first-child {
+            margin-top: 0;
+          }
+          &::after {
+            content: '';
+            position: absolute;
+            width: 5px;
+            height: 5px;
+            background: linear-gradient(
+              162deg,
+              #ff1e3d 0%,
+              #ff0704 51%,
+              #ff7a4f 100%
+            );
+            opacity: 0.6;
+            left: -13px;
+            top: 50%;
+            transform: translateY(-50%);
+            border-radius: 50%;
+          }
+          & > span {
+            color: #ff1a00;
+          }
+          .number {
+            font-family: 'DIN';
+            font-size: 22px;
+            line-height: 1;
+          }
+        }
+      }
+    }
+    .activeContent {
+      margin-top: 16px;
+      width: calc(100% - 20px);
+      height: 187px;
+      background: url('./imgs/boxImg.png') no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      .vipNumber {
+        position: absolute;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        & > :nth-child(1) {
+          font-weight: 600;
+          font-size: 11px;
+          color: #5e2b17;
+          line-height: 16px;
+        }
+        & > :nth-child(2) {
+          font-weight: 500;
+          font-size: 11px;
+          color: rgba(94, 43, 23, 50%);
+          line-height: 16px;
+          text-decoration-line: line-through;
+        }
+        &.vipBuy1 {
+          left: 0;
+          bottom: 60px;
+          width: 33.33%;
+        }
+        &.vipBuy2 {
+          left: 33.33%;
+          bottom: 60px;
+          width: 33.33%;
+          box-sizing: border-box;
+          padding-left: 10px;
+        }
+        &.vipBuy3 {
+          left: 66.66%;
+          bottom: 60px;
+          width: 33.33%;
+          box-sizing: border-box;
+          padding-right: 10px;
+        }
+      }
+      .svipDetails {
+        position: absolute;
+        bottom: 6px;
+        left: 12px;
+        display: flex;
+        align-items: flex-end;
+        line-height: 28px;
+        & > span:nth-child(1) {
+          font-weight: 600;
+          font-size: 18px;
+          color: #ffffff;
+        }
+        & > span:nth-child(2) {
+          margin: 0 4px;
+          font-family: 'DIN';
+          font-size: 30px;
+          color: #ffffff;
+          line-height: 38px;
+        }
+        & > span:nth-child(3) {
+          font-weight: 500;
+          font-size: 12px;
+          color: rgba(255, 255, 255, 0.8);
+        }
+      }
+    }
+  }
+  .activeArea2 {
+    margin-top: 20px;
+    width: 100%;
+    height: 445px;
+  }
+  .activeArea3 {
+    margin-top: 43px;
+    background-color: #fff;
+    width: 100%;
+    border-radius: 20px;
+    border: 2px solid #ffffff;
+    filter: blur(0px);
+    position: relative;
+    padding: 32px 14px 24px 20px;
+    box-sizing: border-box;
+    .tip1 {
+      position: absolute;
+      left: 2px;
+      top: -24px;
+      width: 107px;
+      height: 43px;
+      z-index: 1;
+    }
+    .shadowImg {
+      position: absolute;
+      left: 0;
+      top: 0;
+      z-index: 0;
+      width: 100%;
+      height: 60px;
+      border-radius: 19px 19px 0 0;
+    }
+    .titCon {
+      position: relative;
+      z-index: 2;
+      font-weight: 500;
+      font-size: 15px;
+      color: #333333;
+      line-height: 26px;
+      > span {
+        color: #ff1a00;
+      }
+    }
+    .classCon {
+      margin-top: 16px;
+      width: calc(100% - 6px);
+      height: 128px;
+      background: url('./imgs/class.png') no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      .classBox {
+        position: absolute;
+        left: 50%;
+        transform: translateX(-50%);
+        height: 32px;
+        line-height: 32px;
+        display: flex;
+        align-items: flex-end;
+        font-size: 15px;
+        color: #ffffff;
+        font-weight: 600;
+        font-family: Helvetica Neue, Helvetica, PingFangSC;
+        & > span:nth-child(1) {
+          font-size: 22px;
+          font-weight: 500;
+          line-height: 34px;
+        }
+        & > span {
+          color: #fff176;
+        }
+      }
+    }
+  }
+  .activeArea4 {
+    margin-top: 20px;
+    width: 100%;
+    height: 186px;
+  }
+  .subBtnCon {
+    position: fixed;
+    z-index: 999;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 78px;
+    padding-top: 10px;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+    background-color: #fff;
+    .subBtn {
+      background: url('./imgs/subBtn.png') no-repeat;
+      background-size: 100% 100%;
+      width: 315px;
+      height: 46px;
+      line-height: 46px;
+      font-size: 18px;
+      color: #ffffff;
+      letter-spacing: 1px;
+      text-align: center;
+      font-weight: 600;
+      &.disable {
+        pointer-events: none;
+        opacity: 0.4;
+      }
+    }
+  }
+}
+// 弹窗样式
+.dialogContainer {
+  width: 287px;
+  box-sizing: border-box;
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 16px 24px 22px;
+  text-align: center;
+
+  .dialogTitle {
+    font-weight: 500;
+    font-size: 18px;
+    color: #333333;
+    line-height: 25px;
+  }
+
+  .dialogContent {
+    padding: 16px 0 21px;
+    font-size: 15px;
+    color: #777777;
+    line-height: 26px;
+  }
+
+  .dialogBtnGroup {
+    padding: 0 16px;
+
+    &.orderGroup {
+      display: flex;
+      align-items: center;
+      padding: 0;
+    }
+
+    :global {
+      .van-button {
+        font-weight: 500;
+        font-size: 16px;
+      }
+    }
+  }
+
+  .dialogBtn {
+    margin-left: 12px;
+    color: #ffffff;
+    line-height: 22px;
+  }
+}

+ 375 - 0
src/student/activePage/double12Active/index.tsx

@@ -0,0 +1,375 @@
+import { defineComponent, onMounted, reactive, computed } from 'vue'
+import styles from './index.module.less'
+import ColHeader from '@/components/col-header'
+import { postMessage } from '@/helpers/native-message'
+import request from '@/helpers/request'
+import { useEventListener } from '@vant/use'
+import tip1Img from './imgs/tip1Img.png'
+import tip2Img from './imgs/tip2Img.png'
+import shadowImg from './imgs/shadow.png'
+import svipCon1 from './imgs/svipCon1.png'
+import svipCon2 from './imgs/svipCon2.png'
+import dayjs from 'dayjs'
+import { state as baseState } from '@/state'
+import { Toast, Popup, Button } from 'vant'
+import { useRouter, useRoute } from 'vue-router'
+import { tradeOrder } from '@/student/trade/tradeOrder'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import { memberType, studyCardType } from '@/constant'
+
+export default defineComponent({
+  name: 'double12Active',
+  setup(props, { emit }) {
+    const router = useRouter()
+    const route = useRoute()
+    const state = reactive({
+      titleOpacity: 0,
+      navBarHeight: 0,
+      orderVisiable: false,
+      orderDetail: {} as any,
+      activityId: route.query.activityId
+    })
+    const activitData = reactive({
+      activityStart: '2024-12-12 00:00:00',
+      activityEnd: '2024-12-12 23:59:59',
+      registrationPrice: 0,
+      buyCount: 0, //  buyCount 小于1的时候 代表能无限购买
+      buyNum: 0,
+      vipCardId: 0,
+      activityList: []
+    })
+    const subBtnState = computed(() => {
+      if (Date.now() < new Date(activitData.activityStart).getTime()) {
+        return {
+          disable: true,
+          text: '活动未开始'
+        }
+      } else if (Date.now() > new Date(activitData.activityEnd).getTime()) {
+        return {
+          disable: true,
+          text: '活动已结束'
+        }
+      } else if (activitData.buyCount <= activitData.buyNum) {
+        return {
+          disable: true,
+          text: '您已参与活动'
+        }
+      }
+      return {
+        disable: false,
+        text: '立即购买'
+      }
+    })
+    const userInfo = computed(() => {
+      return baseState.user.data
+    })
+    // 是否为永久会员
+    const isPermanent = computed(() => {
+      return userInfo.value.userVip?.vipType === 'PERMANENT_SVIP' ? true : false
+    })
+    initActivit()
+    function initActivit() {
+      if (!state.activityId) return
+      request
+        .get(
+          `/api-student/memberPriceSettings/getDoubleTwelve/${state.activityId}`
+        )
+        .then(res => {
+          if (res.code === 200 && res.data) {
+            const {
+              activityStart,
+              activityEnd,
+              registrationPrice,
+              buyCount,
+              buyNum,
+              vipCardId,
+              activityRewardList
+            } = res.data || {}
+            activitData.activityStart = activityStart
+            activitData.activityEnd = activityEnd
+            activitData.registrationPrice = registrationPrice
+            activitData.buyCount = buyCount < 1 ? 99999 : buyCount //buyCount 小于1的时候 代表能无限购买
+            activitData.buyNum = buyNum
+            activitData.vipCardId = vipCardId
+            activitData.activityList = (activityRewardList || []).map(item => {
+              const { rewardType, vipCardId, unit } = item.activityReward
+              if (rewardType === 'DISCOUNT') {
+                return {
+                  goodType: rewardType,
+                  goodName: `畅学卡 ${studyCardType[unit]}`,
+                  goodNum: 1,
+                  bizContent: vipCardId,
+                  giftFlag: true,
+                  vipEndDays: null,
+                  goodsNum: 1,
+                  unit
+                }
+              }
+              return {
+                goodType: rewardType,
+                goodName: `小酷Ai ${rewardType} ${memberType[unit]}`,
+                goodNum: 1,
+                bizContent: vipCardId,
+                giftFlag: true,
+                vipEndDays: null,
+                goodsNum: 1,
+                unit
+              }
+            })
+          }
+        })
+        .catch(err => {
+          console.log(err)
+        })
+    }
+    async function handleSubmit() {
+      try {
+        // 永久会员
+        if (isPermanent.value) {
+          Toast('您已是永久SVIP会员')
+          return
+        }
+        const vipType = 'SVIP'
+        // 判断是否有待支付订单
+        const resPadding = await request.post(
+          `/api-student/userOrder/getPendingOrder`,
+          {
+            data: { goodType: vipType }
+          }
+        )
+        console.log(resPadding, 'resPadding')
+        if (resPadding?.data?.id) {
+          state.orderVisiable = true
+          state.orderDetail = resPadding.data || {}
+          return
+        }
+        // 组合订单参数
+        const { data } = await request.post(
+          `/api-student/memberPriceSettings/list`,
+          {
+            data: {
+              status: 1
+            }
+          }
+        )
+
+        const member: any = (data?.list || []).find(item => {
+          return item.id === activitData.vipCardId
+        })
+        if (!member) {
+          Toast('未匹配上活动')
+        }
+        member.title = memberType[member.period]
+        const startTime = dayjs(
+          userInfo.value.userVip.svipEndDate || new Date()
+        ).toDate()
+        const endTime = dayjs(startTime).add(1, 'year').toDate()
+
+        orderStatus.orderObject.orderType = vipType
+        orderStatus.orderObject.orderName = `小酷Ai ${vipType} ${member.title}`
+        orderStatus.orderObject.orderDesc = `小酷Ai ${vipType} ${member.title}`
+        orderStatus.orderObject.actualPrice = activitData.registrationPrice
+        orderStatus.orderObject.recomUserId = ''
+        orderStatus.orderObject.activityId = state.activityId
+        orderStatus.orderObject.orderNo = ''
+        const orderData = {
+          orderType: vipType,
+          goodsName: `小酷Ai ${vipType} ${member.title}`,
+          id: member.id,
+          title: member.title,
+          num: 1, // 购买个数
+          salePrice: member.salePrice,
+          period: member.period,
+          vipEndDays: userInfo.value.userVip?.vipEndDays || 0, // 会员剩余天数
+          svipEndDays: userInfo.value.userVip?.svipEndDays || 0,
+          discount: member.discount, // 是否有折扣
+          discountPrice: member.discountPrice, // 折扣金额
+          price: activitData.registrationPrice,
+          startTime: dayjs(startTime).format('YYYY-MM-DD'),
+          endTime: dayjs(endTime).format('YYYY-MM-DD'),
+          recomUserId: '',
+          activityBuyCount: 0, // 活动购买限制次数
+          activityList: [], // 活动赠送的东西
+          discountEndTime: userInfo.value.discountEndTime, // 畅学卡结束时间
+          discountStartTime: userInfo.value.discountStartTime // 畅学卡开始时间
+        }
+        const canBuyNum = activitData.buyCount - activitData.buyNum
+        if (canBuyNum > 0) {
+          orderData.activityBuyCount = canBuyNum
+          orderData.activityList = activitData.activityList
+        }
+        orderStatus.orderObject.orderList = [orderData]
+        router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: vipType
+          }
+        })
+      } catch {
+        //
+      }
+    }
+    // 取消支付
+    const onCancelOrder = async () => {
+      try {
+        await request.post(`/api-student/userOrder/orderCancel`, {
+          data: { orderNo: state.orderDetail.orderNo }
+        })
+        state.orderVisiable = false
+      } catch {
+        //
+      }
+    }
+    // 继续支付
+    const onContinueOrder = async () => {
+      const orderDetail = state.orderDetail || {}
+      tradeOrder(orderDetail, () => {
+        router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: orderDetail.orderType
+          }
+        })
+      })
+    }
+    onMounted(() => {
+      postMessage({ api: 'getNavHeight' }, res => {
+        const { content } = res as any
+        const dpi = content.dpi || 2
+        if (content.navHeight) {
+          const navHeight = content.navHeight / dpi
+          state.navBarHeight = navHeight
+        }
+      })
+    })
+    useEventListener('scroll', () => {
+      const height =
+        window.scrollY ||
+        window.pageYOffset ||
+        document.documentElement.scrollTop
+      state.titleOpacity = height > 30 ? 1 : 0
+    })
+    return () => (
+      <div
+        class={styles.double12Active}
+        style={{
+          '--navBarHeight': `${state.navBarHeight}px`
+        }}
+      >
+        <ColHeader
+          background={`rgba(255,255,255, ${state.titleOpacity})`}
+          color={`rgba(0,0,0, ${state.titleOpacity})`}
+          backIconColor="black"
+          hideHeader={false}
+          border={false}
+        />
+        <div class={styles.headImg}></div>
+        <div class={styles.activeArea1}>
+          <img class={styles.tip1} src={tip1Img} />
+          <img class={styles.shadowImg} src={shadowImg} />
+          <div class={styles.activeCon}>
+            <div class={styles.activeBox}>活动时间</div>
+            <div class={styles.activeTimes}>
+              {`${dayjs(activitData.activityStart).format(
+                'YYYY年MM月DD日HH:mm'
+              )}-${dayjs(activitData.activityEnd).format(
+                'YYYY年MM月DD日HH:mm'
+              )}`}
+            </div>
+          </div>
+          <div class={styles.activeCon}>
+            <div class={styles.activeBox}>活动内容</div>
+            <div class={styles.activeListCon}>
+              <div class={styles.activeList}>
+                SVIP年度会员限时特惠至
+                <span class={styles.number}>
+                  {' '}
+                  {activitData.registrationPrice}{' '}
+                </span>
+                <span>元</span>
+              </div>
+              <div class={styles.activeList}>
+                活动期间<span>买一年送一年</span>
+              </div>
+              <div class={styles.activeList}>
+                同时额外<span>获赠一年畅学卡</span>,享受约课75折!
+              </div>
+            </div>
+          </div>
+          <div class={styles.activeContent}>
+            <div class={[styles.vipNumber, styles.vipBuy1]}>
+              <div>SVIP年度会员</div>
+              <div>价值1280元</div>
+            </div>{' '}
+            <div class={[styles.vipNumber, styles.vipBuy2]}>
+              <div>SVIP年度会员</div>
+              <div>价值1280元</div>
+            </div>{' '}
+            <div class={[styles.vipNumber, styles.vipBuy3]}>
+              <div>畅学卡一年期</div>
+              <div>价值129元</div>
+            </div>
+            <div class={styles.svipDetails}>
+              <span>¥</span>
+              <span>{activitData.registrationPrice}</span>
+              <span>(SVIP会员买1年赠1年,还送1年畅学)</span>
+            </div>
+          </div>
+        </div>
+        <img class={styles.activeArea2} src={svipCon1} />
+        <div class={styles.activeArea3}>
+          <img class={styles.tip1} src={tip2Img} />
+          <img class={styles.shadowImg} src={shadowImg} />
+          <div class={styles.titCon}>
+            专为音乐学习者设计的全方位学习通行证,畅学卡生效期间,购买任何VIP定制课、趣纠课、直播课、视频课
+            <span>享受75折</span>优惠,助您在音乐道路上更进一步。
+          </div>
+          <div class={styles.classCon}>
+            <div class={styles.classBox}>
+              课程全部<span>75</span>
+              <span>折</span>
+            </div>
+          </div>
+        </div>
+        <img class={styles.activeArea4} src={svipCon2} />
+        <div class={styles.subBtnCon}>
+          <div
+            class={[styles.subBtn, subBtnState.value.disable && styles.disable]}
+            onClick={() => {
+              !subBtnState.value.disable && handleSubmit()
+            }}
+          >
+            {subBtnState.value.text}
+          </div>
+        </div>
+        <Popup
+          v-model:show={state.orderVisiable}
+          style={{ background: 'transparent' }}
+          closeOnClickOverlay={false}
+        >
+          <div class={styles.dialogContainer}>
+            <div class={styles.dialogTitle}>提示</div>
+            <div class={styles.dialogContent}>
+              您有待支付的订单,是否继续支付
+            </div>
+
+            <div class={[styles.dialogBtnGroup, styles.orderGroup]}>
+              <Button round type="default" plain block onClick={onCancelOrder}>
+                取消订单
+              </Button>
+              <Button
+                round
+                type="primary"
+                block
+                class={styles.dialogBtn}
+                onClick={onContinueOrder}
+              >
+                继续支付
+              </Button>
+            </div>
+          </div>
+        </Popup>
+      </div>
+    )
+  }
+})

二進制
src/student/discount-card/images/bg.png


二進制
src/student/discount-card/images/btn-bg.png


二進制
src/student/discount-card/images/card-bg.png


二進制
src/student/discount-card/images/icon-star.png


二進制
src/student/discount-card/images/icon-svip-disabled.png


二進制
src/student/discount-card/images/icon-svip.png


二進制
src/student/discount-card/images/icon-vip-disabled.png


二進制
src/student/discount-card/images/icon-vip.png


二進制
src/student/discount-card/images/memo.png


二進制
src/student/discount-card/images/ring.png


二進制
src/student/discount-card/images/tip.png


二進制
src/student/discount-card/images/title1.png


+ 294 - 0
src/student/discount-card/index.module.less

@@ -0,0 +1,294 @@
+.discountCardContainer {
+  min-height: 100vh;
+  background: url('./images/bg.png') no-repeat top center #cbf3ff;
+  background-size: contain;
+}
+
+.cardContainer {
+  position: relative;
+  padding: 20px 14px 0;
+
+  .imgSection {
+    font-size: 0;
+    padding-bottom: 20px;
+    img {
+      width: 100%;
+      margin-top: 16px;
+    }
+  }
+}
+
+.userSection {
+  display: flex;
+  align-items: flex-start;
+
+  .userImgSection {
+    position: relative;
+    width: 48px;
+    height: 48px;
+    margin-right: 13px;
+    flex-shrink: 0;
+
+    .userImg {
+      width: 48px;
+      height: 48px;
+      border-radius: 50%;
+      overflow: hidden;
+      border: 2px solid #f2f6f7;
+    }
+
+    .showMemeber {
+      position: absolute;
+      bottom: 1px;
+      right: -4px;
+      width: 18px;
+      height: 18px;
+    }
+
+    &.userVip {
+      .showMemeber {
+        background: url('./images/icon-vip-disabled.png') no-repeat center;
+        background-size: contain;
+      }
+
+      &.isVip {
+        .userImg {
+          border: 2px solid #f0af88;
+        }
+
+        .showMemeber {
+          background: url('./images/icon-vip.png') no-repeat center;
+          background-size: contain;
+        }
+      }
+    }
+
+    &.userSVip {
+      .showMemeber {
+        background: url('./images/icon-svip-disabled.png') no-repeat center;
+        background-size: contain;
+      }
+
+      &.isVip {
+        .userImg {
+          border: 2px solid #f0af88;
+        }
+
+        .showMemeber {
+          background: url('./images/icon-svip.png') no-repeat center;
+          background-size: contain;
+        }
+      }
+    }
+  }
+
+  .userInfo {
+    padding-top: 5px;
+    max-width: 185px;
+
+    .userName {
+      display: flex;
+      align-items: center;
+      padding-bottom: 4px;
+    }
+
+    .name {
+      font-weight: 500;
+      font-size: 18px;
+      color: #000000;
+      line-height: 25px;
+      letter-spacing: 1px;
+      max-width: 90px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .phone {
+      font-size: 13px;
+      color: rgba(0, 0, 0, 0.6);
+    }
+
+    .member_time {
+      font-size: 12px;
+      color: #777777;
+      line-height: 17px;
+
+      span {
+        padding-left: 2px;
+        color: #1d88ff;
+        font-weight: 500;
+      }
+    }
+  }
+}
+
+.cardSection {
+  position: relative;
+  background: url('./images/card-bg.png') no-repeat center;
+  background-size: contain;
+  height: 203px;
+  max-width: 347px;
+  margin-top: 27px;
+
+  .top {
+    display: flex;
+    align-items: center;
+    padding-top: 19px;
+    padding-left: 26px;
+    .iconTitle1 {
+      // width: 63px;
+      height: 25px;
+      margin-right: 6px;
+    }
+
+    .priceSection {
+      flex-shrink: 0;
+      display: flex;
+      align-items: center;
+      .currentPrice {
+        display: flex;
+        align-items: flex-end;
+        background: linear-gradient(270deg, #ff7b57 0%, #ff3460 100%);
+        border-radius: 100px 100px 100px 2px;
+        padding: 0 8px;
+        font-weight: 600;
+        color: #ffffff;
+        height: 23px;
+        .l {
+          font-size: 12px;
+          line-height: 17px;
+          padding-right: 2px;
+        }
+        .c {
+          font-size: 20px;
+          line-height: 1;
+        }
+        .r {
+          font-size: 12px;
+          line-height: 17px;
+        }
+      }
+      .originPrice {
+        padding-left: 6px;
+        font-size: 12px;
+        color: rgba(19, 20, 21, 0.4)
+      }
+    }
+  }
+
+  .chapter {
+    position: absolute;
+    right: 19px;
+    top: -14px;
+    background: url('./images/ring.png') no-repeat center;
+    background-size: contain;
+    width: 60px;
+    height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    transform: rotate(-15deg);
+
+    .chapterTop {
+      padding-top: 6px;
+      font-weight: 500;
+      font-size: 11px;
+      color: #ffffff;
+      line-height: 15px;
+    }
+    .chapterBottom {
+      font-family: DINAlternate, DINAlternate;
+      font-weight: bold;
+      font-size: 22px;
+      color: #ffffff;
+      line-height: 1;
+      // display: flex;
+      // align-items: center;
+      i {
+        vertical-align: middle;
+        padding-left: 1px;
+        font-style: normal;
+        font-weight: 500;
+        font-size: 12px;
+        color: #ffffff;
+        line-height: 1;
+      }
+    }
+  }
+}
+
+.btnGroup {
+  background-color: #fff;
+  padding: 10px 30px 30px;
+
+  .submitBtn {
+    background: url('./images/btn-bg.png') no-repeat center;
+    background-size: contain;
+    width: 315px;
+    height: 46px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: 600;
+    font-size: 18px;
+    color: #ffffff;
+    line-height: 26px;
+    i {
+      padding-top: 2px;
+      padding-right: 2px;
+      font-size: 14px;
+      font-style: normal;
+      line-height: 20px;
+    }
+  }
+}
+
+
+// 弹窗样式
+.dialogContainer {
+  width: 287px;
+  box-sizing: border-box;
+  background: #FFFFFF;
+  border-radius: 12px;
+  padding: 16px 24px 22px;
+  text-align: center;
+
+  .dialogTitle {
+    font-weight: 500;
+    font-size: 18px;
+    color: #333333;
+    line-height: 25px;
+  }
+
+  .dialogContent {
+    padding: 16px 0 21px;
+    font-size: 15px;
+    color: #777777;
+    line-height: 26px;
+  }
+
+  .dialogBtnGroup {
+    padding: 0 16px;
+
+    &.orderGroup {
+      display: flex;
+      align-items: center;
+      padding: 0;
+    }
+
+    :global {
+      .van-button {
+        font-weight: 500;
+        font-size: 16px;
+      }
+    }
+  }
+
+  .dialogBtn {
+    margin-left: 12px;
+    color: #FFFFFF;
+    line-height: 22px;
+  }
+}

+ 267 - 0
src/student/discount-card/index.tsx

@@ -0,0 +1,267 @@
+import { computed, defineComponent, onMounted, reactive } from 'vue'
+import styles from './index.module.less'
+import ColHeader from '@/components/col-header'
+import { useEventListener } from '@vant/use'
+import iconStudent from '@common/images/icon_student.png'
+import { Button, Image, Popup } from 'vant'
+import { state as baseState, setLogin } from '@/state'
+
+import iconMemo from './images/memo.png'
+import iconTip from './images/tip.png'
+import iconTitle1 from './images/title1.png'
+import TheSticky from '@/components/the-sticky'
+import request from '@/helpers/request'
+import { tradeOrder } from '../trade/tradeOrder'
+import { useRouter } from 'vue-router'
+import dayjs from 'dayjs'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+
+export default defineComponent({
+  name: 'discount-card',
+  setup() {
+    const router = useRouter()
+    const state = reactive({
+      discountDetail: {} as any,
+      titleOpacity: 0,
+      orderVisible: false,
+      orderDetail: {} as any
+    })
+    const userInfo = computed(() => {
+      const users = baseState.user.data
+      console.log(users, 'users')
+      return {
+        username: users?.username,
+        phone: users?.phone,
+        avatar: users?.heardUrl,
+        id: users?.userId,
+        discountCardFlag: users?.discountCardFlag,
+        discountEndTime: users?.discountEndTime,
+        userVip: users?.userVip
+      }
+    })
+
+    useEventListener('scroll', () => {
+      const height =
+        window.scrollY ||
+        window.pageYOffset ||
+        document.documentElement.scrollTop
+      state.titleOpacity = height > 30 ? 1 : 0
+    })
+
+     // 取消支付
+     const onCancelOrder = async () => {
+      try {
+        await request.post(`${baseState.platformApi}/userOrder/orderCancel`, {
+          data: { orderNo: state.orderDetail.orderNo }
+        })
+        state.orderVisible = false
+      } catch {
+        //
+      }
+    }
+
+    // 继续支付
+    const onContinueOrder = async () => {
+      const orderDetail = state.orderDetail || {}
+      tradeOrder(orderDetail, () => {
+        router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: orderDetail.orderType
+          }
+        })
+      })
+    }
+
+    const onSubmit = async () => {
+      try {
+
+        // 判断是否有待支付订单
+        const resPadding = await request.post(
+          `${baseState.platformApi}/userOrder/getPendingOrder`,
+          {
+            data: { goodType: 'DISCOUNT' }
+          }
+        )
+        console.log(resPadding, 'resPadding')
+        if (resPadding?.data?.id) {
+          state.orderVisible = true
+          state.orderDetail = resPadding.data || {}
+          return
+        }
+
+        let startTime = new Date()
+        if(userInfo.value.discountCardFlag) {
+          startTime = dayjs(userInfo.value.discountEndTime || new Date()).toDate()
+        }
+        let endTime = new Date()
+        if (state.discountDetail.period === 'MONTH') {
+          endTime = dayjs(startTime).add(1, 'month').toDate()
+        } else if (state.discountDetail.period === 'QUARTERLY') {
+          endTime = dayjs(startTime).add(3, 'month').toDate()
+        } else if (state.discountDetail.period === 'YEAR_HALF') {
+          endTime = dayjs(startTime).add(6, 'month').toDate()
+        } else if (state.discountDetail.period === 'YEAR') {
+          endTime = dayjs(startTime).add(1, 'year').toDate()
+        }
+        orderStatus.orderObject.orderType = 'DISCOUNT'
+        orderStatus.orderObject.orderName = `畅学卡`
+        orderStatus.orderObject.orderDesc = `畅学卡`
+        orderStatus.orderObject.actualPrice = state.discountDetail.salePrice || 0
+        orderStatus.orderObject.recomUserId = null
+        orderStatus.orderObject.activityId = null
+        orderStatus.orderObject.orderNo = ''
+
+        orderStatus.orderObject.orderList = [
+          {
+            orderType: 'DISCOUNT',
+            goodsName: `畅学卡`,
+            id: state.discountDetail.id,
+            title: '畅学卡',
+            num: 1, // 购买个数
+            salePrice: state.discountDetail.salePrice,
+            period: state.discountDetail.period,
+            price: state.discountDetail.salePrice,
+            startTime: dayjs(startTime).format('YYYY-MM-DD'),
+            endTime: dayjs(endTime).format('YYYY-MM-DD')
+          }
+        ]
+        router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: 'DISCOUNT'
+          }
+        })
+      } catch {
+        //
+      }
+    }
+
+    onMounted(async () => {
+      try {
+        const userInfo = await request.get(
+          baseState.platformType === 'TEACHER'
+            ? '/api-teacher/teacher/queryUserInfo'
+            : '/api-student/student/queryUserInfo'
+        )
+        setLogin(userInfo.data)
+
+        const { data } = await request.get(
+          `${baseState.platformApi}/memberPriceSettings/getDiscount`
+        )
+        state.discountDetail = data
+      } catch {
+        //
+      }
+      
+    })
+    return () => (
+      <div class={styles.discountCardContainer}>
+        <ColHeader
+          background={`rgba(255,255,255, ${state.titleOpacity})`}
+          backIconColor="black"
+          hideHeader={false}
+          border={false}
+        />
+        <div class={styles.cardContainer}>
+          <div class={styles.userSection}>
+            <div
+              class={[
+                styles.userImgSection,
+                (userInfo.value.userVip.vipType === 'SVIP' ||
+                  userInfo.value.userVip.vipType === 'PERMANENT_SVIP') &&
+                  styles.userSVip,
+                userInfo.value.userVip.vipType === 'VIP' && styles.userVip,
+                userInfo.value.userVip.svipEndDays > 0 ||
+                userInfo.value.userVip.vipEndDays > 0
+                  ? styles.isVip
+                  : ''
+              ]}
+            >
+              <Image
+                class={styles.userImg}
+                src={userInfo.value.avatar || iconStudent}
+                fit="cover"
+              />
+              <i class={styles.showMemeber}></i>
+            </div>
+            <div class={styles.userInfo}>
+              <div class={styles.userName}>
+                <span class={styles.name}>{userInfo.value.username}</span>
+                {userInfo.value.phone && (
+                  <span class={styles.phone}>({userInfo.value.phone})</span>
+                )}
+              </div>
+              <div class={styles.member_time}>
+                {userInfo.value.discountCardFlag ? <>有效期至<span>{dayjs(userInfo.value.discountEndTime).format('YYYY-MM-DD')}</span></> : '您当前尚未开通畅学卡'}
+              </div>
+            </div>
+          </div>
+
+          <div class={styles.cardSection}>
+            <div class={styles.top}>
+              <img src={iconTitle1} class={styles.iconTitle1} />
+              <div class={styles.priceSection}>
+                <div class={styles.currentPrice}>
+                  <div class={styles.l}>¥</div>
+                  <div class={styles.c}>{state.discountDetail.salePrice || 0}</div>
+                  <div class={styles.r}>/年</div>
+                </div>
+                <del class={styles.originPrice}>原价¥{state.discountDetail.originalPrice}/年</del>
+              </div>
+            </div>
+
+            <div class={styles.chapter}>
+              <div class={styles.chapterTop}>课程全部</div>
+              <div class={styles.chapterBottom}>
+                {(state.discountDetail.discountRate || 0) * 100}<i>折</i>
+              </div>
+            </div>
+          </div>
+
+          <div class={styles.imgSection}>
+            <img src={iconMemo} class={styles.iconMemo} />
+            <img src={iconTip} class={styles.iconTip} />
+          </div>
+        </div>
+
+        <TheSticky position="bottom">
+          <div class={styles.btnGroup}>
+            <div class={styles.submitBtn} onClick={onSubmit}>
+              {userInfo.value.discountCardFlag ? '立即续费' : <><i>¥</i>{state.discountDetail.salePrice||0}元立即开通</>}
+            </div>
+          </div>
+        </TheSticky>
+
+
+        <Popup
+          v-model:show={state.orderVisible}
+          style={{ background: 'transparent' }}
+          closeOnClickOverlay={false}
+        >
+          <div class={styles.dialogContainer}>
+            <div class={styles.dialogTitle}>提示</div>
+            <div class={styles.dialogContent}>
+              您有待支付的订单,是否继续支付
+            </div>
+
+            <div class={[styles.dialogBtnGroup, styles.orderGroup]}>
+              <Button round type="default" plain block onClick={onCancelOrder}>
+                取消订单
+              </Button>
+              <Button
+                round
+                type="primary"
+                block
+                class={styles.dialogBtn}
+                onClick={onContinueOrder}
+              >
+                继续支付
+              </Button>
+            </div>
+          </div>
+        </Popup>
+      </div>
+    )
+  }
+})

+ 57 - 31
src/student/live-class/live-detail.tsx

@@ -16,6 +16,8 @@ import LiveItem from '@/views/live-class/live-item'
 import iconShare from '@/views/shop-mall/images/icon-share.svg'
 import { state } from '@/state'
 import { browser } from '@/helpers/utils'
+import { goodsType } from '@/constant';
+import { discountTimer, tradeOrder } from '../trade/tradeOrder'
 interface IProps {
   courseTime: string
   coursePlan: string
@@ -42,7 +44,7 @@ export default defineComponent({
   computed: {
     userInfo() {
       const live = this.live as any
-      console.log('live', live)
+      // console.log('live', live)
       const planList = live.planList || []
       const startTime = planList[0]?.startTime || new Date()
       const endTime = planList[0]?.endTime || new Date()
@@ -155,7 +157,6 @@ export default defineComponent({
             tempObj = item
           }
         })
-        console.log(tempObj, this.live, 'tempObj')
         if (tempObj && tempObj.liveState === 1) {
           postMessage({
             api: 'joinLiveRoom',
@@ -178,26 +179,6 @@ export default defineComponent({
     async onBuy() {
       try {
         const live = this.live
-        orderStatus.orderObject.orderType = 'LIVE'
-        orderStatus.orderObject.orderName = '直播课购买'
-        orderStatus.orderObject.orderDesc = '直播课购买'
-        orderStatus.orderObject.actualPrice = live.coursePrice
-        orderStatus.orderObject.recomUserId = this.recomUserId
-        orderStatus.orderObject.orderNo = ''
-        orderStatus.orderObject.orderList = [
-          {
-            orderType: 'LIVE',
-            goodsName: '直播课购买',
-            courseGroupId: live.courseGroupId,
-            courseGroupName: live.courseGroupName,
-            coursePrice: live.coursePrice,
-            teacherName: live.userName || `游客${live.teacherId || ''}`,
-            teacherId: live.teacherId,
-            avatar: live.avatar,
-            courseInfo: this.courseInfo,
-            recomUserId: this.recomUserId
-          }
-        ]
         // 判断是否是0无订单
         if (live.coursePrice <= 0) {
           await onSubmitZero(() => {
@@ -231,15 +212,40 @@ export default defineComponent({
             confirmButtonText: '继续支付'
           })
             .then(async () => {
-              orderStatus.orderObject.orderNo = result.orderNo
-              orderStatus.orderObject.actualPrice = result.actualPrice
-              orderStatus.orderObject.discountPrice = result.discountPrice
-              orderStatus.orderObject.paymentConfig = {
-                ...result.paymentConfig,
-                paymentVendor: result.paymentVendor,
-                paymentVersion: result.paymentVersion
-              }
-              this.routerTo()
+              // orderStatus.orderObject.orderNo = result.orderNo
+              // orderStatus.orderObject.actualPrice = result.actualPrice
+              // orderStatus.orderObject.discountPrice = result.discountPrice
+              // orderStatus.orderObject.paymentConfig = {
+              //   ...result.paymentConfig,
+              //   paymentVendor: result.paymentVendor,
+              //   paymentVersion: result.paymentVersion
+              // }
+
+              // const orderDetailList = result.orderDetailList || []
+              
+              // let tempObj: any = {}
+              // let discountJson: any = {}
+              // orderDetailList.forEach((item: any) => {
+              //   if(item.goodType === "DISCOUNT") { 
+              //     const users = state.user.data || {}
+              //     tempObj ={
+              //       orderType: item.goodType,
+              //       goodsName: item.goodName,
+              //       id: item.bizId,
+              //       title: item.goodName,
+              //       num: item.goodNum,
+              //       salePrice: item.actualPrice,
+              //       price: item.actualPrice,
+              //       period: item.period,
+              //       ...discountTimer(users.discountEndTime, item.period)
+              //     }
+              //   } else if(item.goodType === 'LIVE') {
+              //     discountJson = item.discountJson ? JSON.parse(item.discountJson) : {}
+              //   }
+              // })
+              // tempObj.discountPrice = discountJson.DISCOUNT || 0
+              // orderStatus.orderObject.orderList.push(tempObj)
+              tradeOrder(result, this.routerTo)
             })
             .catch(() => {
               Dialog.close()
@@ -247,6 +253,26 @@ export default defineComponent({
               this.cancelPayment(result.orderNo)
             })
         } else {
+          orderStatus.orderObject.orderType = 'LIVE'
+          orderStatus.orderObject.orderName = '直播课购买'
+          orderStatus.orderObject.orderDesc = '直播课购买'
+          orderStatus.orderObject.actualPrice = live.coursePrice
+          orderStatus.orderObject.recomUserId = this.recomUserId
+          orderStatus.orderObject.orderNo = ''
+          orderStatus.orderObject.orderList = [
+            {
+              orderType: 'LIVE',
+              goodsName: '直播课购买',
+              courseGroupId: live.courseGroupId,
+              courseGroupName: live.courseGroupName,
+              coursePrice: live.coursePrice,
+              teacherName: live.userName || `游客${live.teacherId || ''}`,
+              teacherId: live.teacherId,
+              avatar: live.avatar,
+              courseInfo: this.courseInfo,
+              recomUserId: this.recomUserId
+            }
+          ]
           this.routerTo()
         }
       } catch {

+ 1 - 1
src/student/practice-class/index.tsx

@@ -164,7 +164,7 @@ export default defineComponent({
         >
           <div ref="headers">
             <ColHeader
-              title="陪练课"
+              title="趣纠课"
               isFixed={false}
               border={false}
               backIconColor="white"

+ 1 - 1
src/student/practice-class/model/all-search.tsx

@@ -25,7 +25,7 @@ export default defineComponent({
       <>
         <div class={styles.filterTitle}>全部筛选</div>
         <div class={styles.searchResult}>
-          <div class={styles.searchTitle}>陪练课数</div>
+          <div class={styles.searchTitle}>趣纠课数</div>
           <RadioGroup
             class={styles['radio-group']}
             modelValue={this.popupParams.expTime}

+ 74 - 52
src/student/teacher-dependent/components/live.module.less

@@ -3,84 +3,79 @@
 }
 
 .liCover {
-  width: 105px;
-  height: 71px;
+  width: 100%;
   border-radius: 4px;
   overflow: hidden;
 }
 
 .liContent {
-  padding-left: 14px;
   height: 100%;
   display: flex;
   flex-direction: column;
   justify-content: space-between;
   .liTitle {
-    font-size: 15px;
-    font-weight: 500;
-    color: #1a1a1a;
-    line-height: 20px;
-    padding-top: 4px;
-    max-width: 180px;
+    font-weight: 600;
+    font-size: 16px;
+    color: #131415;
+    line-height: 22px;
   }
-  // .avatar {
-  //   width: 18px;
-  //   height: 18px;
-  //   overflow: hidden;
-  //   border-radius: 50%;
-  //   margin-right: 6px;
-  // }
+}
+
+.users {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding-top: 8px;
 
-  // .liUserInfo,
-  // .userInfo {
-  //   display: flex;
-  //   align-items: center;
-  //   font-size: 13px;
-  //   color: #999999;
-  //   line-height: 18px;
-  //   flex-wrap: wrap;
-  // }
+  .user {
+    display: flex;
+    align-items: center;
+    img {
+      width: 26px;
+      height: 26px;
+      margin-right: 6px;
+      border-radius: 50%;
+    }
 
-  // .userInfo {
-  //   padding-right: 10px;
-  //   margin-right: 10px;
-  // }
+    span {
+      font-size: 14px;
+      color: #333333;
+      line-height: 20px;
+    }
+  }
+
+  .lean {
+    background: rgba(255, 246, 240, 1);
+    border-radius: 2px;
+    padding: 3px 4px 2px;
+  }
 }
-// .num {
-//   color: #ff802c;
-//   font-size: 13px;
-// }
 
 .buyNum,
 .num {
-  color: #ff802c;
-  // display: flex;
-  // align-items: center;
-  // line-height: 1;
-  font-size: 13px;
+  color: #FF6827;
+  font-size: 12px;
 }
 
 .price {
-  // font-size: 14px;
-  // color: var(--van-primary);
-  // span {
-  //   font-weight: 600;
-  //   font-size: 16px;
-  // }
   font-size: 14px;
   color: #999;
 
   .priceNum {
     color: #ff0000;
-    font-size: 18px;
+    font-size: 22px;
+    // font-family: DINAlternate, DINAlternate;
     font-weight: bold;
     i {
-      font-size: 15px;
+      font-size: 14px;
       font-style: normal;
     }
   }
   .label {
-    padding-left: 8px;
+    // padding-left: 8px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
   }
 }
 
@@ -88,18 +83,41 @@
   border-radius: 10px;
   overflow: hidden;
   margin-bottom: 10px;
+  padding: 12px;
+  background: #FFFFFF;
+
+  .liveTop {
+    display: flex;
+    align-items: center;
+    .iconLive {
+      width: 20px;
+      height: 20px;
+      margin-right: 6px;
+    }
+    span {
+      font-weight: 600;
+      font-size: 14px;
+      color: #333333;
+      line-height: 20px;
+    }
+  }
+
+  .liveCenter {
+    margin: 12px 0 8px;
+    position: relative;
+  }
 }
 
 .subjectName {
   position: absolute;
-  bottom: 4px;
-  left: 0px;
+  top: 8px;
+  left: 8px;
   font-size: 12px;
-  padding: 3px 5px;
+  padding: 3px 5px 2px;
   color: #ffffff;
   line-height: 1;
-  border-radius: 1px;
-  background: rgba(0, 0, 0, 0.29);
+  background: rgba(0,0,0,0.4);
+  border-radius: 3px;
 }
 
 .timerString {
@@ -108,3 +126,7 @@
   align-items: center;
   color: #666;
 }
+
+.tips {
+  margin: 0 14px;
+}

+ 61 - 57
src/student/teacher-dependent/components/live.tsx

@@ -2,13 +2,15 @@ import ColResult from '@/components/col-result'
 import { Cell, CellGroup, List, Image, Icon } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './live.module.less'
-
-import iconTimer from '@common/images/icon_timer2.png'
+import iconLive from '../images/icon-live.png'
+// import icon3 from '../images/icon3.png'
+// import iconTimer from '@common/images/icon_timer2.png'
 import iconTeacher from '@common/images/icon_teacher.png'
-import iconSuccess from '@common/images/icon_success.png'
+// import iconSuccess from '@common/images/icon_success.png'
 import request from '@/helpers/request'
 import dayjs from 'dayjs'
 import { state } from '@/state'
+import Tips from './tips'
 
 export default defineComponent({
   name: 'live',
@@ -36,7 +38,7 @@ export default defineComponent({
       const weekStr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
       // console.log(timeStr.day())
 
-      return timeStr.format('YYYY-MM-DD') + `(${weekStr[timeStr.day()]})`
+      return timeStr.format('YYYY-MM-DD')  + `(${weekStr[timeStr.day()]})`
     },
     async getList() {
       try {
@@ -82,6 +84,12 @@ export default defineComponent({
   render() {
     return (
       <>
+        <Tips
+          type="LIVE"
+          class={styles.tips}
+          title="什么是直播课?"
+          content="直播课是现代教育领域中一种广受欢迎的课程形式,它集实时互动、多媒体展示和高度便利性于一体,为学习者带来了独特且丰富的学习体验。特别是在管乐直播课中,教师可以通过播放经典音乐作品,加深学生对音乐之美的感知与理解。对于那些需要具体操作演示的教学内容,直播课能够提供清晰直观的视角,让教师的每一个动作细节都展现在学生面前,确保学习效果。直播课程的内容围绕特定主题精心设计,旨在满足不同学习者的需求,促进知识与技能的有效传递。"
+        />
         {this.dataShow ? (
           <List
             class={styles.liveList}
@@ -96,60 +104,56 @@ export default defineComponent({
                 border={false}
                 onClick={() => this.onDetail(item)}
               >
-                <Cell
-                  style={{ paddingTop: '19px', paddingBottom: '19px' }}
-                  v-slots={{
-                    icon: () => (
-                      <div style={{ position: 'relative', lineHeight: '0' }}>
-                        <Image
-                          class={styles.liCover}
-                          src={item.backgroundPic}
-                          fit="cover"
-                        />
-                        <span class={styles.subjectName}>
-                          {item?.subjectName}
+                <div class={styles.liveTop}>
+                  <img src={iconLive} class={styles.iconLive} />
+                  <span>开课时间: {this.formatTime(item.salesStartDate)} </span>
+                </div>
+                <div class={styles.liveCenter}>
+                  <Image
+                    class={styles.liCover}
+                    src={item.backgroundPic}
+                    fit="cover"
+                  />
+                  <span class={styles.subjectName}>{item?.subjectName}</span>
+                </div>
+
+                <div class={styles.liContent}>
+                  <div class={[styles.liTitle, 'van-ellipsis']}>
+                    {item.courseGroupName}
+                  </div>
+                  <div class={styles.users}>
+                    {/* <div class={styles.user}>
+                      <img src={item.avatar || iconTeacher} />
+                      <span>{item.teacherName || `游客${item.teacherId || ''}`}</span>
+                    </div> */}
+                    <div class={styles.lean}>
+                      {item.existBuy === 1 ? (
+                        <span class={styles.buyNum}>学习</span>
+                      ) : (
+                        <span class={styles.num}>
+                          {item.studentCount}人学习
                         </span>
-                      </div>
-                    ),
-                    title: () => (
-                      <div class={styles.liContent}>
-                        <div class={[styles.liTitle, 'van-ellipsis']}>
-                          {item.courseGroupName}
-                        </div>
-                        {/* <div class={styles.liUserInfo}>
-                          <div class={[styles.userInfo, 'van-hairline--right']}>
-                            <Image
-                              class={styles.avatar}
-                              fit="cover"
-                              src={item.avatar || iconTeacher}
-                            />
-                            <p>
-                              老师:
-                              {item.teacherName || `游客${item.teacherId}`}
-                            </p>
-                          </div>
-                        </div> */}
-                        <div class={styles.price}>
-                          {item.coursePrice > 0 && (
-                            <>
-                              <span class={styles.priceNum}>
-                                <i>¥</i>
-                                {(this as any).$filters.moneyFormat(
-                                  item.coursePrice
-                                )}
-                              </span>
-                            </>
-                          )}
-                          <span class={styles.label}>
-                            {item.coursePrice > 0 && '/'}
-                            {item.courseNum}课时
+                      )}
+                    </div>
+                    <div class={styles.price}>
+                      {item.coursePrice > 0 && (
+                        <>
+                          <span class={styles.priceNum}>
+                            <i>¥</i>
+                            {(this as any).$filters.moneyFormat(
+                              item.coursePrice
+                            )}
                           </span>
-                        </div>
-                      </div>
-                    )
-                  }}
-                />
-                <Cell
+                        </>
+                      )}
+                      <span class={styles.label}>
+                        {item.coursePrice > 0 && '/'}
+                        {item.courseNum}课时
+                      </span>
+                    </div>
+                  </div>
+                </div>
+                {/* <Cell
                   titleStyle={{ color: '#666666', fontSize: '13px' }}
                   v-slots={{
                     title: () => (
@@ -169,7 +173,7 @@ export default defineComponent({
                       </div>
                     )
                   }}
-                />
+                /> */}
               </CellGroup>
             ))}
           </List>

+ 8 - 5
src/student/teacher-dependent/components/music.tsx

@@ -1,13 +1,14 @@
-import ColResult from '@/components/col-result'
-import { Dialog, List } from 'vant'
 import { defineComponent } from 'vue'
-import styles from './music.module.less'
 import MusicList from '@/views/music/list'
-import { state } from '@/state'
-import { orderStatus } from '@/views/order-detail/orderStatus'
 
 export default defineComponent({
   name: 'music',
+  props: {
+    height: {
+      type: Number,
+      default: 0
+    }
+  },
   data() {
     const query = this.$route.query
     return {
@@ -37,6 +38,8 @@ export default defineComponent({
       <>
         <MusicList
           hideSearch
+          onlySearch
+          height={this.height}
           myself
           onItemClick={this.onItemClick}
           teacherId={this.teacherId}

+ 13 - 2
src/student/teacher-dependent/components/practice.module.less

@@ -1,10 +1,21 @@
 .practice {
-  padding: 14px 14px 0;
+  padding: 4px 14px 80px;
   overflow: hidden;
+  
   .group {
     margin-bottom: 12px;
     border-radius: 10px;
     overflow: hidden;
+
+    :global {
+      .van-cell {
+        padding: 16px 15px;
+        font-size: 16px;
+        .van-cell__title {
+          color: #1A1A1A;
+        }
+      }
+    }
   }
   .price {
     font-size: 14px;
@@ -97,7 +108,7 @@
 }
 
 .arrangeCell {
-  margin: 10px 0 80px !important;
+  margin: 10px 0 0 !important;
   width: auto;
   border-radius: 10px;
   overflow: hidden;

+ 36 - 22
src/student/teacher-dependent/components/practice.tsx

@@ -12,7 +12,8 @@ import {
   Sticky,
   Tag,
   Popup,
-  Toast
+  Toast,
+  Icon
 } from 'vant'
 import { defineComponent } from 'vue'
 import { getWeekCh } from '@/helpers/utils'
@@ -20,6 +21,7 @@ import styles from './practice.module.less'
 import { orderStatus } from '@/views/order-detail/orderStatus'
 import ColResult from '@/components/col-result'
 import { tradeOrder } from '@/student/trade/tradeOrder'
+import Tips from './tips'
 
 export default defineComponent({
   name: 'practice',
@@ -39,6 +41,8 @@ export default defineComponent({
       subjectInfo: {
         subjectPrice: 0,
         courseMinutes: 0,
+        freeMinutes: 0,
+        id: null,
         subjectName: '',
         subjectId: 0
       },
@@ -50,7 +54,7 @@ export default defineComponent({
       selectStatus: false,
       coursePlanList: [] as any,
       calendarDate: dayjs().add(1, 'day').toDate() as Date, // 日历当前时间
-      settingStatus: true, // 是否设置陪练
+      settingStatus: true, // 是否设置趣纠
       loadDataStatus: true // 是否加载数据
     }
   },
@@ -61,7 +65,8 @@ export default defineComponent({
         '/api-student/courseSchedule/getTeacherSubjectPrice',
         {
           params: {
-            teacherId: this.teacherId
+            teacherId: this.teacherId,
+            groupType: 'PRACTICE'
           }
         }
       )
@@ -74,9 +79,11 @@ export default defineComponent({
         })
         // 判断是否有跟学生相同的科目,如果没有则默认取第一个
         const tempRes = findItem || result[0]
-        const { subjectName, subjectPrice, courseMinutes, subjectId } = tempRes
+        const { subjectName, subjectPrice, courseMinutes, subjectId, id, freeMinutes } = tempRes
         this.subjectInfo = {
           subjectPrice,
+          id,
+          freeMinutes,
           courseMinutes,
           subjectName,
           subjectId
@@ -160,6 +167,7 @@ export default defineComponent({
           {
             data: {
               ...params,
+              teacherSubjectPriceId: this.subjectInfo.id,
               studentId: state.user.data?.userId,
               teacherId: this.teacherId
             }
@@ -226,6 +234,7 @@ export default defineComponent({
           '/api-student/courseGroup/lockCourseToCache',
           {
             data: {
+              courseFreeMinutes: this.subjectInfo.freeMinutes,
               courseNum: this.courseNum,
               courseType: 'PRACTICE',
               loop: this.selectType === 'noEnough' ? 1 : 0,
@@ -306,8 +315,8 @@ export default defineComponent({
           ).format('HH:mm')}~${dayjs(item.endTime).format('HH:mm')}`
         })
         orderStatus.orderObject.orderType = 'PRACTICE'
-        orderStatus.orderObject.orderName = subjectInfo.subjectName + '陪练课'
-        orderStatus.orderObject.orderDesc = subjectInfo.subjectName + '陪练课'
+        orderStatus.orderObject.orderName = subjectInfo.subjectName + '趣纠课'
+        orderStatus.orderObject.orderDesc = subjectInfo.subjectName + '趣纠课'
         orderStatus.orderObject.actualPrice = Number(
           (this.courseNum * subjectInfo.subjectPrice).toFixed(2)
         )
@@ -315,11 +324,12 @@ export default defineComponent({
         orderStatus.orderObject.orderList = [
           {
             orderType: 'PRACTICE',
-            goodsName: subjectInfo.subjectName + '陪练课',
-            courseGroupName: subjectInfo.subjectName + '陪练课',
-            courseIntroduce: subjectInfo.subjectName + '陪练课',
+            goodsName: subjectInfo.subjectName + '趣纠课',
+            courseGroupName: subjectInfo.subjectName + '趣纠课',
+            courseIntroduce: subjectInfo.subjectName + '趣纠课',
             subjectId: subjectInfo.subjectId,
             singleCourseMinutes: subjectInfo.courseMinutes,
+            teacherSubjectPriceId: subjectInfo.id,
             courseNum: this.courseNum,
             coursePrice: (this.courseNum * subjectInfo.subjectPrice).toFixed(2),
             teacherName:
@@ -379,10 +389,18 @@ export default defineComponent({
           (this.settingStatus ? (
             <>
               <div class={styles.practice}>
+                <Tips type="PRACTICE" title="什么是趣纠课?" content="趣纠课以一对一专属、高度针对性的形式进行,每次课程时长为25分钟。本课程专为解决学生日常练习中的疑问与误区设计,尤其适合那些在自我练习后感到困惑或不确定自己方法是否正确的学生。不同于传统的教学模式,趣纠课不侧重于新知识或新技能的传授,而是全心全意致力于检查学生现有的练习成果,并及时纠正其中出现的问题。这种方式不仅有助于学生巩固已掌握的知识和技能,还能有效防止错误习惯的形成和发展,为他们今后的学习打下更加坚实的基础。" />
+
                 <CellGroup class={styles.group} border={false}>
+                  <Cell
+                    title="选择专业"
+                    isLink
+                    value={this.subjectInfo.subjectName}
+                    onClick={() => (this.subjectStatus = true)}
+                  />
                   {this.subjectInfo.subjectPrice > 0 && (
                     <Cell
-                      title="陪练课收费"
+                      title="趣纠课收费"
                       v-slots={{
                         default: () => (
                           <div class={styles.price}>
@@ -398,13 +416,6 @@ export default defineComponent({
                       }}
                     />
                   )}
-
-                  <Cell
-                    title="选择专业"
-                    isLink
-                    value={this.subjectInfo.subjectName}
-                    onClick={() => (this.subjectStatus = true)}
-                  />
                   <Cell
                     title="课时数"
                     v-slots={{
@@ -437,8 +448,7 @@ export default defineComponent({
                     />
                   </div>
                 )}
-
-                <Cell
+                {this.showSelectList.length > 0 && <Cell
                   class={[styles.arrangeCell]}
                   v-slots={{
                     title: () => (
@@ -467,7 +477,7 @@ export default defineComponent({
                       </div>
                     )
                   }}
-                ></Cell>
+                ></Cell>}
 
                 <Popup show={this.selectStatus} class={styles.selectPopup}>
                   <div class={styles.selectContainer}>
@@ -540,10 +550,14 @@ export default defineComponent({
                       subjectName,
                       subjectPrice,
                       courseMinutes,
-                      subjectId
+                      id,
+                      subjectId,
+                      freeMinutes
                     } = item
                     this.subjectInfo = {
                       subjectPrice,
+                      id,
+                      freeMinutes,
                       courseMinutes,
                       subjectName,
                       subjectId
@@ -565,7 +579,7 @@ export default defineComponent({
             <ColResult
               btnStatus={false}
               classImgSize="SMALL"
-              tips="老师暂未开放陪练课"
+              tips="老师暂未开放趣纠课"
             />
           ))}
       </>

+ 45 - 0
src/student/teacher-dependent/components/tips/index.module.less

@@ -0,0 +1,45 @@
+
+.tipSection {
+  position: relative;
+  background: #FFFFFF;
+  border-radius: 6px;
+  margin-bottom: 12px;
+  .iconCross {
+    position: absolute;
+    top: 15px;
+    right: 13px;
+    font-size: 16px;
+    color: #CCCCCC;
+    cursor: pointer;
+  }
+  .tipTitle {
+    padding: 12px 12px 10px;
+    font-weight: 500;
+    font-size: 15px;
+    color: #333333;
+    line-height: 18px;
+
+    img {
+      width: 18px;
+      height: 18px;
+      margin-right: 6px;
+      vertical-align: text-bottom;
+    }
+  }
+  .tipContent {
+    padding: 0 12px 10px;
+    font-size: 13px;
+    color: #777777;
+    line-height: 22px;
+    text-align: justify;
+  }
+  .tipFooter {
+    margin: 0 12px;
+    padding: 10px 0;
+    text-align: center;
+    border-top: 1px solid #F2F2F2;
+    font-size: 13px;
+    color: #2DC7AA;
+    line-height: 18px;
+  }
+}

+ 79 - 0
src/student/teacher-dependent/components/tips/index.tsx

@@ -0,0 +1,79 @@
+import { Icon } from 'vant'
+import { defineComponent, onMounted, ref } from 'vue'
+import styles from './index.module.less'
+import icon3 from '../../images/icon3.png'
+
+export default defineComponent({
+  name: 'tips',
+  props: {
+    /** 标题 */
+    title: {
+      type: String,
+      default: '',
+    },
+    /** 内容 */
+    content: {
+      type: String,
+      default: ''
+    },
+    /** 类型 */
+    type: {
+      type: String,
+      default: ''
+    },
+    btnTxt: {
+      type: String,
+      default: '不再提醒'
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const isStatus = ref(true)
+
+    console.log(props.type, '1212')
+
+    // 获取当前提示是否有缓存
+    const getTypeIsCatch = () => {
+      const localType = localStorage.getItem('teacher_home_local');
+      const formatLocalType = localType ? JSON.parse(localType) : {}
+      console.log(formatLocalType[props.type])
+      if(formatLocalType[props.type]) {
+        return true
+      } else {
+        return false
+      }
+    }
+
+    // 设置已知道
+    const setTypeIsCatch = () => {
+      const localType = localStorage.getItem('teacher_home_local');
+      const formatLocalType = localType ? JSON.parse(localType) : {}
+      formatLocalType[props.type] = 1
+      localStorage.setItem('teacher_home_local', JSON.stringify(formatLocalType))
+    }
+
+    onMounted(() => {
+      isStatus.value = !getTypeIsCatch()
+    })
+    return () => (
+      isStatus.value ? <div class={styles.tipSection}>
+      <Icon class={styles.iconCross} onClick={() => {
+        emit('close')
+        isStatus.value = false
+      }} name="cross" />
+      <div class={styles.tipTitle}>
+        <img src={icon3} />
+        {props.title}
+      </div>
+      <div class={styles.tipContent}>
+        {props.content}
+      </div>
+      <div class={styles.tipFooter} onClick={() => {
+        emit("confirm")
+        setTypeIsCatch()
+        isStatus.value = false
+      }}>{props.btnTxt}</div>
+    </div> : ''
+    )
+  }
+})

+ 86 - 0
src/student/teacher-dependent/components/video-item/index.module.less

@@ -0,0 +1,86 @@
+.videoItem {
+  border-radius: 10px;
+  background-color: #fff;
+  overflow: hidden;
+  width: 100%;
+  margin-bottom: 12px;
+  padding: 12px;
+  display: flex;
+  align-items: center;
+
+  .viCover {
+    height: 94px;
+    width: 100%;
+    border-radius: 6px;
+    vertical-align: middle;
+    overflow: hidden;
+  }
+
+  .viSection {
+    margin-left: 10px;
+    flex: 1 auto;
+  }
+
+  .viTitle {
+    font-weight: 600;
+    font-size: 15px;
+    color: #131415;
+    line-height: 21px;
+  }
+
+  .viUserNum {
+    padding-top: 4px;
+    color: #ff802c;
+    font-size: 12px;
+    background: rgba(255, 246, 240, 1);
+    border-radius: 2px;
+    padding: 3px 4px 2px;
+  }
+
+  .viPrice {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    color: #999;
+
+    .priceNum {
+      color: #F44541;
+      font-size: 16px;
+      font-weight: bold;
+
+      i {
+        font-size: 14px;
+        font-style: normal;
+      }
+    }
+
+    
+  }
+
+  .tags {
+    padding: 8px 0;
+    .label {
+      margin-right: 4px;
+      background: #F5F6FA;
+      border-radius: 3px;
+      font-size: 11px;
+      color: #777777;
+      line-height: 16px;
+      padding: 1px 6px;
+      display: inline-block;
+    }
+  }
+
+  .subjectName {
+    position: absolute;
+    top: 4px;
+    left: 4px;
+    font-size: 10px;
+    padding: 3px 5px 2px;
+    color: #ffffff;
+    line-height: 1;
+    background: rgba(0,0,0,0.4);
+    border-radius: 3px;
+  }
+}

+ 86 - 0
src/student/teacher-dependent/components/video-item/index.tsx

@@ -0,0 +1,86 @@
+import { defineComponent, PropType } from 'vue'
+import { Image } from 'vant'
+import styles from './index.module.less'
+
+import iconTeacher from '@common/images/icon_teacher.png'
+
+interface VideoItemProps {
+  id?: number
+  teacherId?: number
+  lessonName: string
+  userName: string
+  avatar: string
+  lessonCoverUrl: string
+  lessonCount: number
+  lessonPrice: number
+  countStudent: number
+  lessonSubjectName: string
+  auditVersion: number
+}
+
+export default defineComponent({
+  name: 'VideoItem',
+  props: {
+    item: Object as PropType<VideoItemProps>,
+    onClick: {
+      type: Function as PropType<(item: any) => void>,
+      default: (item: any) => {}
+    }
+  },
+  render() {
+    const item: any = this.item
+    return (
+      <div
+        class={styles.videoItem}
+        onClick={() => {
+          this.onClick(item)
+        }}
+      >
+        <div style={{ position: 'relative' }}>
+          <Image
+            class={styles.viCover}
+            fit="cover"
+            src={item?.lessonCoverUrl}
+          />
+          <span class={styles.subjectName}>{item?.lessonSubjectName}</span>
+        </div>
+
+        <div class={styles.viSection}>
+          <div class={[styles.viTitle, 'van-ellipsis']}>{item?.lessonName}</div>
+          <div class={styles.tags}>
+            {item?.musicNum > 0 ? <span class={styles.label}>{item?.musicNum}首曲目</span> : ''}
+            {item?.lessonCount > 0 ? <span class={styles.label}>{item?.lessonCount}课时</span> : ''}
+          </div>
+          <div class={styles.viPrice}>
+            <div class={styles.viUserNum}>{item?.countStudent}人学习</div>
+            <span class={styles.priceNum}>
+              {item.payType === 'VIP' ? (
+                <span style={{ color: '#C76E21' }}>会员</span>
+              ) : (
+                <>
+                  {item?.lessonPrice > 0 && (
+                    <>
+                      <i>¥</i>
+                      {item?.lessonPrice}
+                    </>
+                  )}
+                  {item?.lessonPrice <= 0 && item.auditVersion !== 0 && (
+                    <>
+                      <i>¥</i>0
+                    </>
+                  )}
+                  {item?.lessonPrice <= 0 && item.auditVersion === 0 && (
+                    <span style={{ color: '#20BEA0' }}>免费</span>
+                  )}
+                </>
+              )}
+            </span>
+            
+          </div>
+
+          
+        </div>
+      </div>
+    )
+  }
+})

+ 4 - 0
src/student/teacher-dependent/components/video.module.less

@@ -12,3 +12,7 @@
     width: 100%;
   }
 }
+
+.tips {
+  margin: 0 14px;
+}

+ 4 - 2
src/student/teacher-dependent/components/video.tsx

@@ -1,10 +1,11 @@
 import ColResult from '@/components/col-result'
 import request from '@/helpers/request'
 import { state } from '@/state'
-import VideoItem from '@/student/video-class/video-item'
+import VideoItem from './video-item'
 import { List } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './video.module.less'
+import Tips from './tips'
 
 export default defineComponent({
   name: 'VideoList',
@@ -66,7 +67,8 @@ export default defineComponent({
   },
   render() {
     return (
-      <>
+      <> 
+        <Tips type="VIDEO" class={styles.tips} title='什么是视频课?' content='视频课是由教师事先精心准备并录制的课程内容。教师依据教学大纲和目标,系统规划每一节视频的主题与内容,确保教学的连贯性和完整性。在录制过程中,采用专业设备如高清摄像机和录屏软件,保障视频画质清晰、音频质量优良。视频课为学生提供了极大的学习灵活性,他们可以依据个人的时间安排自由选择学习时间,不受地点限制。特别适用于系统化学习体系的内容,视频课能够帮助学生按部就班地掌握知识,实现自主高效学习。' />
         {this.dataShow ? (
           <List
             class={styles.videoList}

+ 168 - 0
src/student/teacher-dependent/components/vip.module.less

@@ -0,0 +1,168 @@
+.practice {
+  padding: 4px 14px 80px;
+  overflow: hidden;
+  
+  .group {
+    margin-bottom: 12px;
+    border-radius: 10px;
+    overflow: hidden;
+
+    :global {
+      .van-cell {
+        padding: 16px 15px;
+        font-size: 16px;
+        .van-cell__title {
+          color: #1A1A1A;
+        }
+      }
+    }
+  }
+  .price {
+    font-size: 14px;
+    color: #999999;
+    span {
+      font-weight: 600;
+      color: #fa6400;
+      font-size: 16px;
+    }
+  }
+
+  :global {
+    .van-stepper--round .van-stepper__minus {
+      color: #333 !important;
+      border: #f3f3f3;
+      background: #f3f3f3;
+    }
+
+    .van-stepper--round .van-stepper__plus {
+      background: var(--van-primary);
+    }
+  }
+}
+
+.rTag {
+  padding: 10px 0;
+  .tag {
+    background: #e9fff8;
+    margin-bottom: 8px;
+  }
+}
+
+.selectPopup {
+  width: 312px;
+  background: #ffffff;
+  border-radius: 8px;
+  .selectContainer {
+    padding: 18px 14px;
+  }
+  .rTitle {
+    font-size: 18px;
+  }
+  .selectPopupContent {
+    padding: 20px 0;
+  }
+  .desc,
+  .times {
+    font-size: 14px;
+    color: #666666;
+    line-height: 20px;
+  }
+
+  .times {
+    padding-top: 15px;
+    span {
+      display: block;
+    }
+  }
+
+  .selectBtn {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .btn {
+      width: 48%;
+    }
+  }
+}
+
+.rTitle {
+  display: flex;
+  align-items: center;
+  font-size: 16px;
+  color: #333;
+  font-weight: 500;
+  &::before {
+    margin-right: 8px;
+    content: ' ';
+    display: inline-block;
+    width: 4px;
+    height: 17px;
+    background: linear-gradient(180deg, #59e5d5 0%, #2dc7aa 100%);
+    border-radius: 3px;
+  }
+}
+
+.protocol {
+  padding: 0 14px;
+  background-color: #f6f8f9;
+}
+
+.arrangeCell {
+  margin: 10px 0 0 !important;
+  width: auto;
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.fixedBtn {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 9;
+}
+
+
+.tipSection {
+  position: relative;
+  background: #FFFFFF;
+  border-radius: 6px;
+  margin-bottom: 12px;
+  .iconCross {
+    position: absolute;
+    top: 15px;
+    right: 13px;
+    font-size: 16px;
+    color: #CCCCCC;
+    cursor: pointer;
+  }
+  .tipTitle {
+    padding: 12px 12px 10px;
+    font-weight: 500;
+    font-size: 15px;
+    color: #333333;
+    line-height: 18px;
+
+    img {
+      width: 18px;
+      height: 18px;
+      margin-right: 6px;
+      vertical-align: text-bottom;
+    }
+  }
+  .tipContent {
+    padding: 0 12px 10px;
+    font-size: 13px;
+    color: #777777;
+    line-height: 22px;
+  }
+  .tipFooter {
+    margin: 0 12px;
+    padding: 10px 0;
+    text-align: center;
+    border-top: 1px solid #F2F2F2;
+    font-size: 13px;
+    color: #2DC7AA;
+    line-height: 18px;
+  }
+}

+ 588 - 0
src/student/teacher-dependent/components/vip.tsx

@@ -0,0 +1,588 @@
+import Calendar from '@/business-components/calendar'
+import request from '@/helpers/request'
+import { state } from '@/state'
+import dayjs from 'dayjs'
+import {
+  ActionSheet,
+  Button,
+  Cell,
+  CellGroup,
+  Dialog,
+  Stepper,
+  Sticky,
+  Tag,
+  Popup,
+  Toast,
+  Icon
+} from 'vant'
+import { defineComponent } from 'vue'
+import { getWeekCh } from '@/helpers/utils'
+import styles from './practice.module.less'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import ColResult from '@/components/col-result'
+import { tradeOrder } from '@/student/trade/tradeOrder'
+import icon3 from '../images/icon3.png'
+import Tips from './tips'
+
+export default defineComponent({
+  name: 'VIP_COURSE',
+  props: {
+    userInfo: {
+      type: Object,
+      default: {}
+    }
+  },
+  data() {
+    const query = this.$route.query
+    return {
+      teacherId: query.teacherId,
+      subjectId: query.subjectId,
+      teacherSubjectList: [],
+      subjectStatus: false,
+      subjectInfo: {
+        freeMinutes: 0,
+        subjectPrice: 0,
+        courseMinutes: 0,
+        id: null,
+        subjectName: '',
+        subjectId: 0
+      },
+      courseNum: 4,
+      calendarStatus: false,
+      calendarList: [] as any,
+      selectCourseList: [] as any,
+      coursePlanStatus: false,
+      selectStatus: false,
+      coursePlanList: [] as any,
+      calendarDate: dayjs().add(1, 'day').toDate() as Date, // 日历当前时间
+      settingStatus: true, // 是否设置VIP定制课
+      loadDataStatus: true // 是否加载数据
+    }
+  },
+  async mounted() {
+    try {
+      this.loadDataStatus = true
+      const res = await request.get(
+        '/api-student/courseSchedule/getTeacherSubjectPrice',
+        {
+          params: {
+            teacherId: this.teacherId,
+            groupType: 'VIP'
+          }
+        }
+      )
+      
+      const result = res.data || []
+      if (result.length > 0) {
+        const userSubjectId = this.subjectId || state.user.data?.subjectId
+        const findItem = result.find((item: any) => {
+          return item.subjectId === Number(userSubjectId)
+        })
+        // 判断是否有跟学生相同的科目,如果没有则默认取第一个
+        const tempRes = findItem || result[0]
+        const { subjectName, subjectPrice, courseMinutes, subjectId, id, freeMinutes } = tempRes
+        this.subjectInfo = {
+          subjectPrice,
+          freeMinutes,
+          id,
+          courseMinutes,
+          subjectName,
+          subjectId
+        }
+
+        result.forEach((item: any) => {
+          item.name = item.subjectName
+        })
+        this.teacherSubjectList = result
+
+        this.getList()
+
+        this.onBuy(true)
+
+        this.settingStatus = true
+      } else {
+        this.settingStatus = false
+      }
+
+      // 判断如果是审核的则不显示
+      const resVersion = await request.post('/api-teacher/open/appVersion', {
+        data: {
+          platform:
+            state.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher',
+          version: state.version
+        }
+      })
+      this.settingStatus =  resVersion.data.check ? false : true
+      this.loadDataStatus = false
+    } catch {
+      this.loadDataStatus = false
+    }
+  },
+  computed: {
+    showSelectList() {
+      const arr: any = this.selectCourseList
+      let list = [...arr]
+      list.forEach((item: any) => {
+        item.title =
+          dayjs(item.startTime).format('YYYY-MM-DD') +
+          ' ' +
+          getWeekCh(dayjs(item.startTime).day()) +
+          ' ' +
+          item.start +
+          '~' +
+          item.end
+      })
+      return list
+    },
+    selectType() {
+      // 循环次数是否足够
+      return this.selectCourseList.length < this.courseNum
+        ? 'noEnough'
+        : 'enough'
+    }
+  },
+  methods: {
+    async onSubmit() {
+      if (this.selectCourseList.length <= 0) {
+        Toast('请选择课程时间')
+        return
+      }
+
+      if (this.selectCourseList.length < this.courseNum) {
+        this.selectStatus = true
+        return
+      }
+
+      await this._lookCourse()
+    },
+    async getList(date?: Date) {
+      try {
+        const tempDate = date || dayjs().add(1, 'day').toDate()
+        let params = {
+          day: dayjs(tempDate).format('DD'),
+          month: dayjs(tempDate).format('MM'),
+          year: dayjs(tempDate).format('YYYY')
+        }
+        let res = await request.post(
+          '/api-student/courseSchedule/createPracticeCourseCalendar',
+          {
+            data: {
+              ...params,
+              teacherSubjectPriceId: this.subjectInfo.id,
+              studentId: state.user.data?.userId,
+              teacherId: this.teacherId
+            }
+          }
+        )
+        const result = res.data || []
+        let tempObj = {}
+        result.forEach((item: any) => {
+          tempObj[item.date] = item
+        })
+        this.calendarList = tempObj
+        this.calendarStatus = result.length > 0
+      } catch {}
+    },
+    onSelectDay(obj: any) {
+      const result = obj || []
+      let list = [...this.selectCourseList] as any
+
+      result.forEach((item: any) => {
+        const isExist = list.some(
+          (course: any) => course.startTime === item.startTime
+        )
+        !isExist && list.push({ ...item })
+      })
+      // 去掉不在
+      let tempList: any[] = []
+      list.forEach((item: any) => {
+        const isExist = result.some(
+          (course: any) => course.startTime === item.startTime
+        )
+        isExist && tempList.push(item)
+      })
+      // 对数组进行排序
+      tempList.sort((first: any, second: any) => {
+        if (first.startTime > second.startTime) return 1
+        if (first.startTime < second.startTime) return -1
+        return 0
+      })
+      console.log(tempList, 'list')
+      this.selectCourseList = [...tempList] as any
+    },
+    onCloseTag(item: any) {
+      Dialog.confirm({
+        title: '提示',
+        message: '您是否要删除该选择的课程?',
+        confirmButtonColor: 'var(--van-primary)'
+      }).then(() => {
+        const index = this.selectCourseList.findIndex(
+          (course: any) => course.startTime === item.startTime
+        )
+        this.selectCourseList.splice(index, 1)
+      })
+    },
+    async _lookCourse(callBack?: Function) {
+      try {
+        let times = [] as any
+        this.selectCourseList.forEach((item: any) => {
+          times.push({
+            startTime: item.startTime,
+            endTime: item.endTime
+          })
+        })
+        const res = await request.post(
+          '/api-student/courseGroup/lockCourseToCache',
+          {
+            data: {
+              courseFreeMinutes: this.subjectInfo.freeMinutes,
+              courseNum: this.courseNum,
+              courseType: 'VIP',
+              loop: this.selectType === 'noEnough' ? 1 : 0,
+              teacherId: this.teacherId,
+              timeList: [...times]
+            }
+          }
+        )
+        const result = res.data || []
+        result.forEach((item: any, index: number) => {
+          this.coursePlanList[index] = {
+            ...this.coursePlanList[index],
+            startTime: item.startTime,
+            endTime: item.endTime,
+            classNum: index + 1
+          }
+        })
+        this.coursePlanStatus = true
+        this.selectStatus = true
+        callBack && callBack()
+      } catch (e: any) {
+        // 报错时需要重置日历表的数据
+        const message = e.message
+        Dialog.alert({
+          title: '提示',
+          confirmButtonColor: 'var(--van-primary)',
+          message
+        }).then(() => {
+          this.getList(this.calendarDate || new Date())
+          this.selectCourseList = []
+          this.selectStatus = false
+        })
+      }
+    },
+    async onReset() {
+      // 是否有锁课状态 或 是锁课类型的
+      if (this.coursePlanStatus || this.selectType === 'enough') {
+        this.selectStatus = false
+        setTimeout(() => {
+          this.coursePlanList = []
+        }, 500)
+      } else if (this.selectType === 'noEnough') {
+        this.selectStatus = false
+      }
+      setTimeout(() => {
+        this.coursePlanStatus = false
+      }, 500)
+    },
+    async onSure() {
+      const status = this.coursePlanStatus
+      await this._lookCourse(() => {
+        if (status) {
+          this.selectStatus = false
+          this.onBuy()
+        }
+      })
+    },
+    async onBuy(goTo?: boolean) {
+      try {
+        const res = await request.post(
+          '/api-student/userOrder/getPendingOrder',
+          {
+            data: {
+              goodType: 'VIP_COURSE',
+              bizId: this.teacherId
+            }
+          }
+        )
+        const subjectInfo = this.subjectInfo
+        const tempCourseList = [...this.coursePlanList]
+        // console.log(this.coursePlanList)
+        tempCourseList.forEach((item: any) => {
+          item.classDate = dayjs(item.startTime).format('YYYY-MM-DD')
+          item.title = `${dayjs(item.startTime).format(
+            'YYYY-MM-DD'
+          )} ${getWeekCh(dayjs(item.startTime).day())} ${dayjs(
+            item.startTime
+          ).format('HH:mm')}~${dayjs(item.endTime).format('HH:mm')}`
+        })
+        orderStatus.orderObject.orderType = 'VIP_COURSE'
+        orderStatus.orderObject.orderName = subjectInfo.subjectName + 'VIP定制课'
+        orderStatus.orderObject.orderDesc = subjectInfo.subjectName + 'VIP定制课'
+        orderStatus.orderObject.actualPrice = Number(
+          (this.courseNum * subjectInfo.subjectPrice).toFixed(2)
+        )
+        orderStatus.orderObject.orderNo = ''
+        orderStatus.orderObject.orderList = [
+          {
+            orderType: 'VIP_COURSE',
+            goodsName: subjectInfo.subjectName + 'VIP定制课',
+            courseGroupName: subjectInfo.subjectName + 'VIP定制课',
+            courseIntroduce: subjectInfo.subjectName + 'VIP定制课',
+            subjectId: subjectInfo.subjectId,
+            singleCourseMinutes: subjectInfo.courseMinutes,
+            teacherSubjectPriceId: subjectInfo.id,
+            courseNum: this.courseNum,
+            coursePrice: (this.courseNum * subjectInfo.subjectPrice).toFixed(2),
+            teacherName:
+              this.userInfo.username || `游客${this.userInfo.userId || ''}`,
+            teacherId: this.userInfo.userId,
+            starGrade: this.userInfo.starGrade,
+            avatar: this.userInfo.heardUrl,
+            classTime: tempCourseList
+          }
+        ]
+        const result = res.data
+        if (result) {
+          Dialog.confirm({
+            title: '提示',
+            message: '您有一个未支付的订单,是否继续支付?',
+            confirmButtonColor: '#269a93',
+            cancelButtonText: '取消订单',
+            confirmButtonText: '继续支付'
+          })
+            .then(async () => {
+              tradeOrder(result, this.routerTo)
+              // this.routerTo()
+            })
+            .catch(() => {
+              Dialog.close()
+              // 只用取消订单,不用做其它处理
+              this.cancelPayment(result.orderNo)
+            })
+        } else {
+          !goTo && this.routerTo()
+        }
+      } catch {}
+    },
+    routerTo() {
+      this.$router.push({
+        path: '/orderDetail',
+        query: {
+          orderType: 'VIP_COURSE'
+        }
+      })
+    },
+    async cancelPayment(orderNo: string) {
+      try {
+        await request.post('/api-student/userOrder/orderCancel', {
+          data: {
+            orderNo
+          }
+        })
+        // this.routerTo()
+      } catch {}
+    }
+  },
+  render() {
+    return (
+      <>
+        {!this.loadDataStatus &&
+          (this.settingStatus ? (
+            <>
+              <div class={styles.practice}>
+                <Tips type="LIVE_COURSE" title='什么是VIP定制课?' content='VIP定制课程采用一对一专属授课模式,每节课时长为45分钟。课程内容根据学生的具体需求量身打造,旨在全面提升学生的个人技能与表现。不论是希望在乐器演奏技巧上取得突破,如提高指法精准度、气息控制能力或节奏掌握等;还是为即将到来的重要活动、比赛或考级做充分准备,我们都能提供高度匹配的教学方案。此外,教学进度将根据每位学员的学习吸收情况灵活调整,确保每个人都能在最适合自己的节奏中稳步前进,扎实提升个人能力。' />
+                <CellGroup class={styles.group} border={false}>
+                  <Cell
+                    title="选择专业"
+                    isLink
+                    value={this.subjectInfo.subjectName}
+                    onClick={() => (this.subjectStatus = true)}
+                  />
+                  {this.subjectInfo.subjectPrice > 0 && (
+                    <Cell
+                      title="VIP定制课收费"
+                      v-slots={{
+                        default: () => (
+                          <div class={styles.price}>
+                            <span>
+                              ¥
+                              {(this as any).$filters.moneyFormat(
+                                this.subjectInfo.subjectPrice
+                              )}
+                            </span>
+                            /{this.subjectInfo.courseMinutes}分钟
+                          </div>
+                        )
+                      }}
+                    />
+                  )}
+                  <Cell
+                    title="课时数"
+                    v-slots={{
+                      default: () => (
+                        <Stepper
+                          v-model={this.courseNum}
+                          theme="round"
+                          max={12}
+                          min={1}
+                          buttonSize={22}
+                          onChange={() => {
+                            this.selectCourseList = []
+                          }}
+                        />
+                      )
+                    }}
+                  />
+                </CellGroup>
+
+                {this.calendarStatus && (
+                  <div class={styles.group}>
+                    <Calendar
+                      selectList={this.selectCourseList}
+                      list={this.calendarList}
+                      maxDays={this.courseNum}
+                      nextMonth={(date: Date) => this.getList(date)}
+                      prevMonth={(date: Date) => this.getList(date)}
+                      selectDay={this.onSelectDay}
+                      v-model:calendarDate={this.calendarDate}
+                    />
+                  </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>}
+
+                <Popup show={this.selectStatus} class={styles.selectPopup}>
+                  <div class={styles.selectContainer}>
+                    <div class={styles.rTitle}>
+                      <span>提示</span>
+                    </div>
+                    <div class={styles.selectPopupContent}>
+                      <p class={styles.desc}>
+                        {this.selectType === 'noEnough' &&
+                        !this.coursePlanStatus
+                          ? '您所选择的上课时间未达到您输入的课时数,系统根据已选时间将自动按周顺延排课。'
+                          : '您已选择以下上课时间段,时间段会暂时锁定,锁定期间学员不可购买该时间段课程。'}
+                      </p>
+                      {this.coursePlanList &&
+                        this.coursePlanList.length > 0 &&
+                        this.coursePlanStatus && (
+                          <p class={styles.times}>
+                            {this.coursePlanList.map((item: any) => (
+                              <span>
+                                {dayjs(item.startTime || new Date()).format(
+                                  'YYYY-MM-DD'
+                                )}{' '}
+                                {dayjs(item.startTime || new Date()).format(
+                                  'HH:mm'
+                                )}
+                                ~
+                                {dayjs(item.endTime || new Date()).format(
+                                  'HH:mm'
+                                )}
+                              </span>
+                            ))}
+                          </p>
+                        )}
+                    </div>
+
+                    <div class={styles.selectBtn}>
+                      <Button
+                        class={styles.btn}
+                        type="primary"
+                        round
+                        block
+                        plain
+                        onClick={this.onReset}
+                      >
+                        {this.selectType === 'noEnough'
+                          ? '继续选择'
+                          : '重新选择'}
+                      </Button>
+                      <Button
+                        class={styles.btn}
+                        type="primary"
+                        round
+                        block
+                        onClick={this.onSure}
+                      >
+                        确认
+                      </Button>
+                    </div>
+                  </div>
+                </Popup>
+
+                <ActionSheet
+                  show={this.subjectStatus}
+                  actions={this.teacherSubjectList}
+                  cancelText="取消"
+                  closeOnClickAction
+                  onCancel={() => (this.subjectStatus = false)}
+                  onSelect={(item: any) => {
+                    const {
+                      subjectName,
+                      subjectPrice,
+                      courseMinutes,
+                      id,
+                      subjectId,
+                      freeMinutes
+                    } = item
+                    this.subjectInfo = {
+                      subjectPrice,
+                      freeMinutes,
+                      id,
+                      courseMinutes,
+                      subjectName,
+                      subjectId
+                    }
+                    this.subjectStatus = false
+                  }}
+                />
+              </div>
+              <div
+                class={['btnGroup', styles.fixedBtn]}
+                style={{ background: '#fff', paddingTop: '10px' }}
+              >
+                <Button block round type="primary" onClick={this.onSubmit}>
+                  确认约课
+                </Button>
+              </div>
+            </>
+          ) : (
+            <ColResult
+              btnStatus={false}
+              classImgSize="SMALL"
+              tips="老师暂未开放VIP定制课"
+            />
+          ))}
+      </>
+    )
+  }
+})

二進制
src/student/teacher-dependent/images/icon-add.png


二進制
src/student/teacher-dependent/images/icon-cert.png


二進制
src/student/teacher-dependent/images/icon-live.png


二進制
src/student/teacher-dependent/images/icon-message.png


二進制
src/student/teacher-dependent/images/icon-small-live.png


二進制
src/student/teacher-dependent/images/icon1.png


二進制
src/student/teacher-dependent/images/icon2.png


二進制
src/student/teacher-dependent/images/icon3.png


二進制
src/student/teacher-dependent/images/icon_subject1.png


二進制
src/student/teacher-dependent/images/icon_video.png


二進制
src/student/teacher-dependent/model/fans-list/images/fans-bg.png


二進制
src/student/teacher-dependent/model/fans-list/images/icon-close.png


+ 80 - 0
src/student/teacher-dependent/model/fans-list/index.module.less

@@ -0,0 +1,80 @@
+.fansSection {
+  width: 300px;
+  background: #ffffff;
+  border-radius: 10px;
+
+  .fansBg {
+    height: 113px;
+  }
+
+  .noFans {
+    padding: 22px 0 30px;
+    font-size: 14px;
+    color: #999999;
+    line-height: 20px;
+    text-align: center;
+  }
+
+  .fansList {
+    padding: 20px 15px 0;
+  }
+}
+
+.fansGroup {
+  padding: 0 0 20px !important;
+  display: flex;
+  align-items: center;
+
+  :global {
+    .van-cell__title {
+      flex: 1 auto;
+    }
+    .van-cell__value {
+      flex: 1 auto;
+      flex-shrink: 0;
+      // width: 68px;
+    }
+
+    .van-button {
+      --van-button-small-height: 26px;
+      min-width: 68px;
+    }
+    .van-button--disabled {
+      opacity: 1;
+      background: #F4F5F6;
+      color: #AAAAAA;
+      border-color: #F4F5F6;
+    }
+  }
+
+  p {
+    font-size: 14px;
+    color: #999999;
+    line-height: 20px;
+    width: 160px;
+  }
+}
+
+.fansImage {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  overflow: hidden;
+  flex-shrink: 0;
+}
+
+.fansTitle {
+  
+  font-size: 16px;
+  color: #1a1a1a;
+  line-height: 22px;
+
+  .title {
+    max-width: 130px;
+    padding-left: 12px;
+  }
+  .introduce {
+    padding-left: 12px;
+    width: 140px;
+  }
+}

+ 84 - 0
src/student/teacher-dependent/model/fans-list/index.tsx

@@ -0,0 +1,84 @@
+import { defineComponent } from "vue";
+import {Button, Cell, Image} from 'vant'
+import { postMessage } from '@/helpers/native-message'
+import styles from './index.module.less'
+import fansBg from './images/fans-bg.png'
+import icon_fans from '../../images/icon_fans.png'
+
+export default defineComponent({
+  name: 'fans-list',
+  props: {
+    fansList: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['confirm'],
+  setup(props, { emit }) {
+    const onDetail = async (item) => {
+      // 申请入群
+      if (!item.hasWaitAuditFlag && !item.existFlag) {
+        // this.chatStatus = true
+        // this.chatItem = item
+        emit('confirm', item)
+        return
+      }
+
+      // 进入群聊天
+      if (item.existFlag) {
+        postMessage({
+          api: 'joinChatGroup',
+          content: {
+            type: 'multi', // single 单人 multi 多人
+            id: item.id
+          }
+        })
+      }
+    }
+    return () => <div class={styles.fansSection}>
+      <img src={fansBg} class={styles.fansBg} />
+      {props.fansList && props.fansList.length > 0 ? (
+      <div class={styles.fansList}>
+        {props.fansList.map((item: any) => (
+          <Cell
+            center
+            class={styles.fansGroup}
+            border={false}
+            v-slots={{
+              icon: () => (
+                <Image
+                  src={item.img || icon_fans}
+                  fit="cover"
+                  class={styles.fansImage}
+                />
+              ),
+              title: () => (
+                <div class={styles.fansTitle}>
+                  <div class={[styles.title, 'van-ellipsis']}>
+                    {item.name}
+                  </div>
+                  <p class={["van-ellipsis", styles.introduce]}>{item.introduce}</p>
+                </div>
+              ),
+              default: () => (
+                <Button
+                  type="primary"
+                  size="small"
+                  round
+                  disabled={item.hasWaitAuditFlag}
+                  onClick={() => onDetail(item)}
+                >
+                  {item.existFlag ? '去聊天' : ''}
+                  {item.hasWaitAuditFlag ? '审核中' : ''}
+                  {!item.hasWaitAuditFlag && !item.existFlag
+                    ? '申请入群'
+                    : ''}
+                </Button>
+              )
+            }}
+          />
+        ))}
+      </div>
+    ) : <div class={styles.noFans}>您还没有粉丝群哦!</div>}</div>
+  }
+})

+ 77 - 43
src/student/teacher-dependent/model/teacher-header.module.less

@@ -1,6 +1,6 @@
 .headerContent {
-  padding-top: 40px;
-  padding-bottom: 20px;
+  // padding-top: 12px;
+  // padding-bottom: 20px;
   min-height: 100px;
   position: relative;
 }
@@ -11,9 +11,14 @@
   flex-shrink: 0;
 }
 
+.teacherUs {
+  padding-left: 20px;
+  flex: 1 auto;
+}
+
 .teacherIcon {
   position: relative;
-  margin-top: -38px;
+  // margin-top: -38px;
   line-height: 0;
 
   .avatar {
@@ -42,13 +47,12 @@
 .teacherInfo {
   display: flex;
   align-items: center;
-  padding: 14px 0;
+  padding: 14px 0 0;
 
   .teacherInfoName {
-    font-size: 20px;
-    font-weight: 500;
-    color: #1a1a1a;
-    // line-height: 22px;
+    font-weight: 600;
+    font-size: 22px;
+    color: #FFFFFF;
     max-width: 150px;
     overflow: hidden;
     white-space: nowrap;
@@ -69,8 +73,8 @@
   line-height: 16px;
   color: #666;
   font-weight: 500;
-  padding-bottom: 12px;
-  padding-top: 10px;
+  padding-top: 4px;
+  padding-left: 8px;
 
   .score {
     margin-left: 25px;
@@ -78,44 +82,69 @@
 }
 
 .headerCount {
-  width: calc(100% - 28px);
-  padding: 12px;
+  width: 100%;
+  padding: 14px 14px 20px;
   margin: 0 auto;
-  background-color: #fff;
   border-radius: 10px;
   box-sizing: border-box;
 }
 
 .teacherOperation {
+  padding-top: 18px;
+  display: flex;
   :global {
     .van-button {
-      height: 28px;
+      height: 36px;
+      border-radius: 6px;
+      background: rgba(255, 255, 255, .2);
+      border: none;
+      color: #FFFFFF;
+      font-size: 14px;
+      font-weight: 600;
     }
   }
 
   .btn {
     padding: 3px 12px 1px;
-    min-width: 62px;
+    flex: 1;
+    img {
+      width: 18px;
+      // height: 18px;
+      vertical-align: sub;
+      margin-right: 6px;
+
+    }
   }
 
+
   .btnStar {
-    color: #f18400;
-    border-color: #f18400;
+    
+    
+    color: #F8F9FC;
+    background: #2DC7AA;
+    margin-right: 13px;
+    // &::before {
+    //   background: #2DC7AA;
+    // }
   }
 }
 
 .subjectSection {
   margin-right: 10px;
   // height: 18px;
-  max-width: 68px;
+  max-width: 44px;
   box-sizing: content-box;
 }
 
 .teacher-bottom {
-  padding: 30px 0 0 0;
+  padding: 17px 0 0 0;
   display: flex;
   align-items: center;
-  justify-content: space-between;
+
+  .iconCert {
+    height: 18px;
+    margin-right: 8px;
+  }
 }
 
 .followFans {
@@ -128,14 +157,17 @@
   justify-content: space-between;
 
   .teacher-data_item {
-    font-size: 14px;
-    color: #333333;
+    font-size: 13px;
+    color: rgba(255,255,255,0.6);
     position: relative;
+    line-height: 1.2;
 
     span {
       font-weight: 500;
-      color: #000000;
-      font-size: 20px;
+      font-family: DINAlternate, DINAlternate;
+      font-weight: bold;
+      font-size: 15px;
+      color: #fff;
       margin-left: 5px;
     }
 
@@ -207,7 +239,7 @@
 
 .subjectList {
   overflow: auto;
-  width: 255px;
+  width: 285px;
   // height: 18px;
   display: flex;
   flex-wrap: nowrap;
@@ -216,17 +248,18 @@
 .piNameSubject {
   display: flex;
   align-items: center;
+  padding-top: 20px;
 
   .subject {
     display: flex;
     align-items: center;
-    margin-left: 4px;
-    background: #effbf9;
-    border-radius: 8px;
+    margin-left: 8px;
+    background: rgba(255, 255, 255, .2);
+    border-radius: 10px;
     font-size: 12px;
-    line-height: 16px;
-    color: #2dc7aa;
-    padding: 0 5px;
+    line-height: 1.3;
+    color: #FFFFFF;
+    padding: 4px 8px 3px;
     white-space: nowrap;
 
     &:first-child {
@@ -252,18 +285,19 @@
 
 .liveTag {
   position: absolute;
-  bottom: 0;
+  top: -1.5px;
   left: 50%;
   transform: translateX(-50%);
-  line-height: 16px;
-  background: #ff6363;
-  border-radius: 20px;
-  text-align: center;
-  color: #fff;
-  font-size: 10px;
-  font-weight: 500;
-  padding: 2px 0;
-  width: 60%;
+  width: 34px;
+  // line-height: 16px;
+  // background: #ff6363;
+  // border-radius: 20px;
+  // text-align: center;
+  // color: #fff;
+  // font-size: 10px;
+  // font-weight: 500;
+  // padding: 2px 0;
+  // width: 60%;
   z-index: 10;
 }
 
@@ -362,8 +396,8 @@
     }
 
     .avatar {
-      width: 60px;
-      height: 60px;
+      width: 72px;
+      height: 72px;
     }
 
     .piNameSubject {

+ 80 - 122
src/student/teacher-dependent/model/teacher-header.tsx

@@ -4,9 +4,10 @@ import styles from './teacher-header.module.less'
 import { postMessage } from '@/helpers/native-message'
 import iconTeacher from '@common/images/icon_teacher.png'
 import request from '@/helpers/request'
-import IconXueli from '@common/images/icon-xueli.png'
-import IconJiaozi from '@common/images/icon-jiaozi.png'
-import IconChat from '../images/icon-chat.png'
+// import IconXueli from '@common/images/icon-xueli.png'
+// import IconJiaozi from '@common/images/icon-jiaozi.png'
+// import IconChat from '../images/icon-chat.png'
+import iconSmallLive from '../images/icon-small-live.png'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `../images/${fileName}`
@@ -163,7 +164,7 @@ export default defineComponent({
                 />
 
                 {this.userInfo.liveing === 1 && (
-                  <p class={styles.liveTag}>直播中</p>
+                  <img src={iconSmallLive}  class={styles.liveTag} />
                 )}
 
                 {(this.checkBadge('SVIP') || this.checkBadge('VIP')) && (
@@ -179,132 +180,89 @@ export default defineComponent({
                   />
                 )}
               </div>
-              <div class={styles.teacherOperation}>
-                <Button
-                  type="primary"
-                  size="small"
-                  plain
-                  round
-                  class={[
-                    styles.btn,
-                    this.userInfo.isStar ? styles.btnStar : ''
-                  ]}
-                  onClick={this.onStart}
-                >
-                  {/* {!this.userInfo.isStar && <Icon name="plus" />} */}
-
-                  {this.userInfo.isStar ? '已关注' : '关注'}
-                </Button>
-                <Button
-                  type="primary"
-                  size="small"
-                  round
-                  style={{ marginLeft: '5px' }}
-                  class={styles.btn}
-                  icon={IconChat}
-                  onClick={() => {
-                    postMessage({
-                      api: 'joinChatGroup',
-                      content: {
-                        type: 'single', // single 单人 multi 多人
-                        id: this.userInfo.imUserId
-                        // id: this.teacherId
-                      }
-                    })
-                  }}
-                >
-                  {/* <Icon name={} size="16" style={{ marginRight: '3px' }} /> */}
-                  聊天
-                </Button>
-              </div>
-            </div>
-            <div class={styles.teacherInfo}>
-              <div class={styles.teacherInfoName}>
-                {this.userInfo.username || `游客${this.userInfo.userId || ''}`}
+              <div class={styles.teacherUs}>
+                <div class={styles.teacherInfo}>
+                  <div class={styles.teacherInfoName}>
+                    {this.userInfo.username ||
+                      `游客${this.userInfo.userId || ''}`}
+                  </div>
+                  <div class={styles.teacherHonor}>
+                    {/* <div class={styles.score}>评分:</div> */}
+                    <div class={styles.level}>
+                      {this.starGrade ? (
+                        <Rate
+                          readonly
+                          modelValue={this.starGrade}
+                          iconPrefix="iconfont"
+                          color="#FFC459"
+                          void-icon="star_default"
+                          icon="star_active"
+                          size={15}
+                        />
+                      ) : (
+                        ''
+                      )}
+                    </div>
+                  </div>
+                </div>
+                <div class={styles['teacher-bottom']}>
+                  <img src={getAssetsHomeFile('icon-cert.png')} class={styles.iconCert} />
+                  <div class={styles['teacher-data']}>
+                    <div class={styles['teacher-data_item']}>
+                      粉丝 <span>{this.userInfo.fansNum || 0}</span>
+                    </div>
+                    <div class={styles['teacher-data_item']}>
+                      已上课时 <span>{this.userInfo.expTime || 0}</span>
+                    </div>
+                  </div>
+                </div>
               </div>
-              {this.userInfo.degreeFlag ? <img src={IconXueli} /> : null}
-              {this.userInfo.teacherFlag ? <img src={IconJiaozi} /> : null}
             </div>
-            <div class={styles.teacherHonor}>
-              <div>勋章:</div>
-              <div class={styles.teacherIcons} onClick={this.openTeacherIcon}>
-                <Image
-                  class={styles.iconOther}
-                  src={
-                    this.checkBadge('STYLE')
-                      ? getAssetsHomeFile('cert_active.png')
-                      : getAssetsHomeFile('cert_default.png')
-                  }
-                />
-                <Image
-                  class={styles.iconOther}
-                  src={
-                    this.checkBadge('VIDEO')
-                      ? getAssetsHomeFile('video_active.png')
-                      : getAssetsHomeFile('video_default.png')
-                  }
-                />
-                <Image
-                  class={styles.iconOther}
-                  src={
-                    this.checkBadge('LIVE')
-                      ? getAssetsHomeFile('live_active.png')
-                      : getAssetsHomeFile('live_default.png')
-                  }
-                />
+            <div class={styles.piNameSubject}>
                 <Image
-                  class={styles.iconOther}
-                  src={
-                    this.checkBadge('MUSIC')
-                      ? getAssetsHomeFile('music_active.png')
-                      : getAssetsHomeFile('music_default.png')
-                  }
+                  class={styles.subjectSection}
+                  src={getAssetsHomeFile('icon_subject1.png')}
+                  fit="contain"
                 />
-              </div>
-              <div class={styles.score}>评分:</div>
-              <div class={styles.level}>
-                {this.starGrade ? (
-                  <Rate
-                    readonly
-                    modelValue={this.starGrade}
-                    iconPrefix="iconfont"
-                    color="#FFC459"
-                    void-icon="star_default"
-                    icon="star_active"
-                    size={15}
-                  />
-                ) : (
-                  <span style={{ fontSize: '12px', color: '#999999' }}>
-                    暂无评分
-                  </span>
-                )}
-              </div>
-            </div>
-            <div class={styles.piNameSubject}>
-              <Image
-                class={styles.subjectSection}
-                src={getAssetsHomeFile('icon_subject.png')}
-                fit="contain"
-              />
-              <div class={styles.subjectList}>
-                {this.subjectNameList.map((item: any) => (
-                  <span class={styles.subject}>{item}</span>
-                ))}
-              </div>
-            </div>
-            <div class={styles['teacher-bottom']}>
-              <div class={styles['teacher-data']}>
-                <div class={styles['teacher-data_item']}>
-                  粉丝 <span>{this.userInfo.fansNum || 0}</span>
-                </div>
-                <div class={styles['teacher-data_item']}>
-                  已上课时 <span>{this.userInfo.expTime || 0}</span>
+                <div class={styles.subjectList}>
+                  {this.subjectNameList.map((item: any) => (
+                    <span class={styles.subject}>{item}</span>
+                  ))}
                 </div>
               </div>
+            <div class={styles.teacherOperation}>
+              <Button
+                type="primary"
+                size="small"
+                class={[styles.btn, styles.btnStar]}
+                onClick={this.onStart}
+              >
+                {!this.userInfo.isStar ? <img src={getAssetsHomeFile('icon-add.png')} /> : ''}
+                {/* <img src={getAssetsHomeFile('icon-add.png')} /> */}
+                {this.userInfo.isStar ? '已关注' : '关注'}
+              </Button>
+              <Button
+                type="primary"
+                size="small"
+                class={styles.btn}
+                onClick={() => {
+                  postMessage({
+                    api: 'joinChatGroup',
+                    content: {
+                      type: 'single', // single 单人 multi 多人
+                      id: this.userInfo.imUserId
+                      // id: this.teacherId
+                    }
+                  })
+                }}
+              >
+                <img src={getAssetsHomeFile('icon-message.png')} />
+                聊天
+              </Button>
             </div>
           </div>
         </div>
-        <Popup class={styles['teaherPopup']} v-model:show={this.iconShow}>
+        {/* <Popup class={styles['teaherPopup']} v-model:show={this.iconShow}>
           <Image src={getAssetsHomeFile('teacher-icon.png')} />
           <div class={styles.teacherIconWrap}>
             {iconList.map(n => {
@@ -324,7 +282,7 @@ export default defineComponent({
             class={styles.closeIcon}
             src={getAssetsHomeFile('icon-close.png')}
           />
-        </Popup>
+        </Popup> */}
       </>
     )
   }

+ 398 - 0
src/student/teacher-dependent/teacher-follow.module.less

@@ -0,0 +1,398 @@
+.headerContent {
+  padding-top: 40px;
+  padding-bottom: 20px;
+  min-height: 100px;
+  position: relative;
+}
+
+.teacherContent {
+  display: flex;
+  justify-content: space-between;
+  flex-shrink: 0;
+}
+
+.teacherIcon {
+  position: relative;
+  margin-top: -38px;
+  line-height: 0;
+
+  .avatar {
+    position: relative;
+    width: 78px;
+    height: 78px;
+    box-sizing: border-box;
+    border: 2px solid #fff;
+    background-color: #fff;
+  }
+
+  .avatarActive {
+    border-color: #F0AF88;
+    background-color: #F0AF88;
+  }
+
+  .teacherIconVip {
+    position: absolute;
+    bottom: 0;
+    right: -12px;
+    width: 39px;
+    height: 18px;
+  }
+}
+
+.teacherInfo {
+  display: flex;
+  align-items: center;
+  padding: 14px 0;
+
+  .teacherInfoName {
+    font-size: 20px;
+    font-weight: 500;
+    color: #1a1a1a;
+    // line-height: 22px;
+    max-width: 150px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+
+  &>img {
+    margin-left: 10px;
+    width: 39px;
+    height: 16px;
+  }
+}
+
+.teacherHonor {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+  line-height: 16px;
+  color: #666;
+  font-weight: 500;
+  padding-bottom: 12px;
+  padding-top: 10px;
+
+  .score {
+    margin-left: 0;
+  }
+}
+
+.headerCount {
+  width: calc(100% - 28px);
+  padding: 12px;
+  margin: 0 auto;
+  background-color: #fff;
+  border-radius: 10px;
+  box-sizing: border-box;
+}
+
+.teacherOperation {
+  :global {
+    .van-button {
+      height: 28px;
+    }
+  }
+
+  .btn {
+    padding: 3px 12px 1px;
+    min-width: 62px;
+  }
+
+  .btnStar {
+    color: #f18400;
+    border-color: #f18400;
+  }
+}
+
+.subjectSection {
+  margin-right: 10px;
+  // height: 18px;
+  max-width: 68px;
+  box-sizing: content-box;
+}
+
+.teacher-bottom {
+  padding: 30px 0 0 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.followFans {
+  padding-top: 10px !important;
+}
+
+.teacher-data {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .teacher-data_item {
+    font-size: 14px;
+    color: #333333;
+    position: relative;
+
+    span {
+      font-weight: 500;
+      color: #000000;
+      font-size: 20px;
+      margin-left: 5px;
+    }
+
+    &::after {
+      content: ' ';
+      display: inline-block;
+      position: absolute;
+      right: 0px;
+      top: 2px;
+      width: 1px;
+      height: 16px;
+      background: #ebebeb;
+    }
+
+    &:first-child {
+      padding-right: 15px;
+      margin-right: 15px;
+    }
+
+    &:last-child {
+      &::after {
+        display: none;
+      }
+    }
+  }
+}
+
+.iconVip {
+  width: 34px !important;
+  margin-right: 5px;
+}
+
+.iconOther {
+  margin-left: 6px;
+  width: 18px;
+  height: 18px;
+}
+
+.teacher-info {
+  margin-left: 8px;
+
+  .teacher-name {
+    font-size: 18px;
+    font-weight: 500;
+    color: #1a1a1a;
+    padding-bottom: 6px;
+    display: flex;
+    justify-content: space-between;
+
+    .teacherCert {
+      display: flex;
+      align-items: center;
+    }
+
+    .teacherLast {
+      display: inline-block;
+      max-width: 120px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+}
+
+.subjectList::-webkit-scrollbar {
+  display: none;
+  /* Chrome Safari */
+}
+
+.subjectList {
+  overflow: auto;
+  width: 255px;
+  // height: 18px;
+  display: flex;
+  flex-wrap: nowrap;
+}
+
+.piNameSubject {
+  display: flex;
+  align-items: center;
+
+  .subject {
+    display: flex;
+    align-items: center;
+    margin-left: 4px;
+    background: #effbf9;
+    border-radius: 8px;
+    font-size: 12px;
+    line-height: 16px;
+    color: #2dc7aa;
+    padding: 0 5px;
+    white-space: nowrap;
+
+    &:first-child {
+      margin-left: 0;
+    }
+  }
+}
+
+.rTitle {
+  display: flex;
+  align-items: center;
+
+  &::before {
+    margin-right: 8px;
+    content: ' ';
+    display: inline-block;
+    width: 4px;
+    height: 14px;
+    background: #2dc7aa;
+    border-radius: 3px;
+  }
+}
+
+.liveTag {
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  line-height: 16px;
+  background: #ff6363;
+  border-radius: 20px;
+  text-align: center;
+  color: #fff;
+  font-size: 10px;
+  font-weight: 500;
+  padding: 2px 0;
+  width: 60%;
+  z-index: 10;
+}
+
+.teacherName {
+  display: inline-block;
+  flex: 1;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100px;
+}
+
+.teacherIcons {
+  display: flex;
+  align-items: center;
+}
+
+.teaherPopup {
+  background: transparent;
+  width: 276px;
+  overflow: initial;
+}
+
+.teacherIconWrap {
+  padding: 50px 22px 22px 22px;
+  background-color: #fff;
+  border-radius: 0 0 10px 10px;
+  margin-top: -25px;
+}
+
+.teacherIconItem {
+  margin-bottom: 16px;
+
+  .teacherIconItemTop {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+  }
+
+  :global {
+    .van-image {
+      margin-right: 8px;
+      width: 24px;
+      height: 24px;
+    }
+  }
+
+  .teacherIconTitle {
+    font-size: 16px;
+    font-weight: bold;
+    color: #333;
+  }
+
+  .teacherIconDes {
+    font-size: 12px;
+    line-height: 16px;
+    font-weight: 400;
+    padding-left: 3px;
+    color: #666;
+  }
+}
+
+.closeIcon {
+  position: absolute;
+  bottom: -54px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 0.96rem;
+  height: 0.96rem;
+}
+
+.liveList {
+  padding: 12px 0;
+
+  .headerFollow {
+    margin-bottom: 12px;
+
+    .teacherIcon {
+      margin: 0 10px 0 0;
+    }
+
+    .score {
+      margin-left: 10px;
+    }
+
+    .teacherIconVip {
+      left: 50%;
+      right: initial;
+      transform: translateX(-50%);
+      bottom: -6px;
+    }
+
+    .liveTag {
+      // bottom: -30px;
+      width: 80%;
+    }
+
+    .avatar {
+      width: 60px;
+      height: 60px;
+    }
+
+    .piNameSubject {
+      align-items: flex-start;
+    }
+
+    .subjectList {
+      flex-wrap: wrap;
+    }
+
+    .subject {
+      margin: 2px 5px 3px 0;
+    }
+
+    .teacher-bottom {
+      padding-top: 24px;
+    }
+
+    .unlinkeBtn {
+      font-size: 12px;
+      color: #ff6363;
+      border: 1px solid #ff6363;
+      padding: 5px 10px;
+      border-radius: 20px;
+    }
+  }
+}
+
+.followContainer {
+  display: flex;
+  align-items: flex-start;
+}

+ 7 - 8
src/student/teacher-dependent/teacher-follow.tsx

@@ -1,9 +1,7 @@
 import { Cell, Dialog, Icon, Image, List, Rate, Sticky, Toast } from 'vant'
 import { defineComponent } from 'vue'
-import styles from './model/teacher-header.module.less'
+import styles from './teacher-follow.module.less'
 import iconTeacher from '@common/images/icon_teacher.png'
-import musicCert from '@common/images/music_cert.png'
-import teacherCert from '@common/images/teacher_cert.png'
 import request from '@/helpers/request'
 import ColResult from '@/components/col-result'
 import { postMessage } from '@/helpers/native-message'
@@ -12,6 +10,7 @@ import IconXueli from '@common/images/icon-xueli.png'
 import IconJiaozi from '@common/images/icon-jiaozi.png'
 import dayjs from 'dayjs'
 import ColHeader from '@/components/col-header'
+import TheSticky from '@/components/the-sticky'
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `./images/${fileName}`
   const modules = import.meta.globEager('./images/*')
@@ -137,10 +136,10 @@ export default defineComponent({
   render() {
     return (
       <div class={styles.teacherFollow}>
-        <Sticky position="top" offsetTop={0}>
-          <ColHeader border={false} />
+        <TheSticky position="top">
+          <ColHeader border={false} isFixed={false} />
           <ColSearch onSearch={this.onSearch} />
-        </Sticky>
+        </TheSticky>
         {this.dataShow ? (
           <List
             class={styles.liveList}
@@ -261,7 +260,7 @@ export default defineComponent({
                   </div>
                 </div>
                 <div class={styles.teacherHonor}>
-                  <div>勋章:</div>
+                  {/* <div>勋章:</div>
                   <div class={styles.teacherIcons}>
                     <Image
                       class={styles.iconOther}
@@ -295,7 +294,7 @@ export default defineComponent({
                           : getAssetsHomeFile('music_default.png')
                       }
                     />
-                  </div>
+                  </div> */}
                   <div class={styles.score}>评分: </div>
                   <div class={styles.level}>
                     {item.starGrade ? (

+ 79 - 5
src/student/teacher-dependent/teacher-home.module.less

@@ -8,18 +8,35 @@
     .van-nav-bar{
       transition: all .3s;
     }
+    .van-tabs {
+      // --van-tabs-line-height: 22px;
+    }
+    .van-tabs__nav--line {
+      padding-bottom: 0;
+    }
     .van-tab {
-      margin-top: 12px;
-      margin-bottom: 5px;
+      // margin-top: 12px;
+      // margin-bottom: 5px;
       padding: 0 14px;
+      font-size: 16px;
+      color: #000000;
+      position: relative;
+      z-index: 9;
+      // --van-tab-line-height: 22px;
     }
     .van-tab--active {
-      font-size: 16px !important;
-      color: #333333;
+      font-weight: 600;
     }
     .van-button--plain.van-button--primary {
       background-color: transparent;
     }
+    .van-tabs__line {
+      width: 44px !important;
+      height: 6px;  
+      background: linear-gradient( 90deg, #2DC7AA 0%, rgba(45,199,170,0) 100%) !important;
+      border-radius: 3px;
+      bottom: 15px;
+    }
   }
 }
 .bgImg{
@@ -35,7 +52,7 @@
   top: 0;
   left: 0;
   width: 100%;
-  height: 188px;
+  height: 238px;
   background-color: rgba(0, 0, 0, .6);
   backdrop-filter: blur(10px);
   -webkit-backdrop-filter: blur(10px);
@@ -44,3 +61,60 @@
   position: relative;
   z-index: 10;
 }
+
+
+.singleSection {  
+  background: #F8F9FC;
+  border-radius: 10px 10px 0px 0px;
+  padding: 15px 14px 6px;
+  .btnType {
+    display: flex;
+    .btn2, .btn1 {
+      flex: 1;
+      font-weight: 500;
+      font-size: 14px;
+      color: #333333;
+      line-height: 36px;
+      border-radius: 6px;
+      text-align: center;
+      img {
+        vertical-align: middle;
+        width: 16px;
+        height: 16px;
+        margin-right: 6px;
+      }
+    }
+    .btn1 {
+      
+      background: linear-gradient( 310deg, rgba(92,214,214,0.07) 0%, rgba(255,255,255,0.03) 50%, rgba(255,255,255,0) 100%, rgba(255,255,255,0) 100%);
+      border: 1px solid #E3EFED;
+      margin-right: 10px;
+    }
+    .btn2 {
+      background: linear-gradient( 310deg, rgba(214,96,92,0.07) 0%, rgba(255,255,255,0.04) 50%, rgba(255,255,255,0) 100%, rgba(255,255,255,0) 100%);
+      border: 1px solid #FFEBE5;
+    }
+  }
+
+  .singleContent {
+    background: #FFFFFF;
+    border-radius: 6px;
+    margin-top: 12px;
+    font-size: 13px;
+    color: #777777;
+    line-height: 22px;
+    padding: 12px;
+    text-align: justify;
+  }
+}
+
+.fansPopup {
+  background: transparent;
+
+  :global {
+    .van-popup__close-icon {
+      font-size: 18px;
+      color: #CCCCCC;
+    }
+  }
+}

+ 129 - 26
src/student/teacher-dependent/teacher-home.tsx

@@ -1,8 +1,7 @@
 import ColHeader from '@/components/col-header'
-import { defineComponent } from 'vue'
+import { defineComponent, provide } from 'vue'
 import styles from './teacher-home.module.less'
-import { Tab, Tabs } from 'vant'
-import Single from './components/single'
+import { Popup, Tab, Tabs } from 'vant'
 import Practice from './components/practice'
 import Live from './components/live'
 import VideoList from './components/video'
@@ -13,6 +12,11 @@ import { useEventListener, useWindowScroll } from '@vueuse/core'
 import TeacherHeader from './model/teacher-header'
 import { useRect } from '@vant/use'
 import { useEventTracking } from '@/helpers/hooks'
+import Vip from './components/vip'
+import Single from './components/single'
+import JoinChat from './model/join-chat'
+import FansList from './model/fans-list'
+import MusicList from '@/views/music/list'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `./images/${fileName}`
@@ -28,17 +32,28 @@ export default defineComponent({
     const query = this.$route.query
     return {
       teacherId: query.teacherId as string,
-      tabs: tabs || query.tabs || 'single',
+      tabs: tabs || query.tabs || 'vip',
       userInfo: {} as any,
       background: 'rgba(55, 205, 177, 0)',
       headColor: '#fff',
       height: 'auto' as any,
+      tabsHeight: 0,
+      stickyHeightNum: 0,
       backIconColor: 'white',
-      homeContaiterHeight: ''
+      homeContaiterHeight: '',
+      fansStatus: false,
+      fansList: [],
+      chatItem: {},
+      chatStatus: false,
     }
   },
-  async created() {},
   async mounted() {
+    this.$nextTick(() => {
+      const { height } = useRect(document.querySelector('.van-tabs__wrap') as any)
+      this.tabsHeight = height
+      this.stickyHeight()
+    })
+
     this.getTeacherDetail()
     // 监听页面返回
     listenerMessage('webViewOnResume', () => {
@@ -46,8 +61,6 @@ export default defineComponent({
     })
     useEventListener(document, 'scroll', evt => {
       const { y } = useWindowScroll()
-      // this.background = `rgba(255, 255, 255, ${y.value / 100})`
-      // console.log(y.value) 142
       if (y.value > 52) {
         this.headColor = '#000'
         this.background = '#fff'
@@ -61,6 +74,16 @@ export default defineComponent({
     useEventTracking('达人风采')
   },
   methods: {
+    stickyHeight() {
+      let height = 0
+      if(this.height === 'auto') {
+        height = this.tabsHeight
+      } else {
+        height = this.height + this.tabsHeight
+      }
+      this.stickyHeightNum = height;
+      (this.$refs.musicListRef as any)?.updateStickyHeight(height)
+    },
     async getTeacherDetail() {
       try {
         const res = await request.get('/api-student/teacher/queryTeacherHome', {
@@ -70,6 +93,25 @@ export default defineComponent({
         })
         this.userInfo = res.data
       } catch {}
+    },
+    async getFansList() {
+      try {
+        const res = await request.post('/api-student/imGroup/queryTeacherGroup', {
+          data: {
+            type: 'FAN',
+            createUserId: this.teacherId
+          }
+        })
+        this.fansList = res.data || []
+      } catch {}
+    },
+    onItemClick(item: any) {
+      this.$router.push({
+        path: '/music-detail',
+        query: {
+          id: item.id
+        }
+      })
     }
   },
   render() {
@@ -77,6 +119,7 @@ export default defineComponent({
       <div class={styles['teacher-record']}>
         <div ref="headers">
           <ColHeader
+            hideHeader={false}
             background={this.background}
             border={false}
             color={this.headColor}
@@ -84,14 +127,14 @@ export default defineComponent({
             onHeaderBack={() => {
               this.$nextTick(() => {
                 const { height } = useRect((this as any).$refs.headers)
-                this.height = height
-                // this.homeContaiterHeight = `calc(100vh - var(--van-tabs-line-height) - ${height}px - 15px)`
+                this.height = Math.floor(height)
+                this.stickyHeight()
               })
             }}
           />
         </div>
         <img class={styles.bgImg} src={this.userInfo.heardUrl} />
-        <div class={styles.bg}></div>
+        <div class={styles.bg} style={{ paddingTop: this.height + 'px' }}></div>
         <div class={styles.teacherHeader}>
           <TeacherHeader
             userInfo={this.userInfo}
@@ -101,12 +144,42 @@ export default defineComponent({
               this.userInfo.fansNum = val.fansNum
             }}
           />
+
+          <div class={styles.singleSection}>
+            <div class={styles.btnType}>
+              <div class={styles.btn1} onClick={() => {
+                this.$router.push({
+                  path: '/teacher-style',
+                  query: {
+                    teacherId: this.teacherId
+                  }
+                })
+              }}>
+                <img src={getAssetsHomeFile('icon1.png')} />
+                个人风采
+              </div>
+              <div class={styles.btn2} onClick={async () => {
+                if(this.fansList.length <= 0) {
+                  await this.getFansList()
+                }
+                this.fansStatus = true
+              }}>
+                <img src={getAssetsHomeFile('icon2.png')} />
+                粉丝群
+              </div>
+            </div>
+
+              {this.userInfo.introduction && <div class={styles.singleContent}>
+              {this.userInfo.introduction}
+            </div>}
+            
+          </div>
         </div>
         <Tabs
           color="var(--van-primary)"
           background="#f8f9fc"
           shrink
-          lineWidth={20}
+          lineWidth={44}
           sticky
           offsetTop={this.height}
           v-model:active={this.tabs}
@@ -114,12 +187,19 @@ export default defineComponent({
             sessionStorage.setItem('teacherHomeTabs', this.tabs as string)
           }}
         >
-          <Tab title="个人风采" name="single">
+          {/* <Tab title="个人风采" name="single">
             <div style={{ minHeight: this.homeContaiterHeight }}>
               {this.tabs === 'single' && <Single userInfo={this.userInfo} />}
             </div>
+          </Tab> */}
+          <Tab title="VIP定制课" name="vip">
+            <div style={{ minHeight: this.homeContaiterHeight }}>
+              {this.tabs === 'vip' && (
+                <Vip userInfo={this.userInfo} />
+              )}
+            </div>
           </Tab>
-          <Tab title="陪练课" name="practice">
+          <Tab title="趣纠课" name="practice">
             <div style={{ minHeight: this.homeContaiterHeight }}>
               {this.tabs === 'practice' && (
                 <Practice userInfo={this.userInfo} />
@@ -132,24 +212,47 @@ export default defineComponent({
             </div>
           </Tab>
           <Tab title="视频课" name="video">
-            <div style={{ minHeight: this.homeContaiterHeight }}>
               {this.tabs === 'video' && <VideoList />}
-            </div>
           </Tab>
           <Tab title="乐谱" name="music">
-            <div style={{ minHeight: this.homeContaiterHeight }}>
-              {this.tabs === 'music' && <Music />}
-            </div>
+            {this.tabs === 'music' && <MusicList
+                hideSearch
+                onlySearch
+                myself
+                height={this.stickyHeightNum}
+                ref="musicListRef"
+                onItemClick={this.onItemClick}
+                teacherId={this.teacherId}
+              />
+            }
           </Tab>
         </Tabs>
 
-        {/* <div class={styles.container}>
-          {this.tabs === 'single' && <Single userInfo={this.userInfo} />}
-          {this.tabs === 'practice' && <Practice userInfo={this.userInfo} />}
-          {this.tabs === 'live' && <Live />}
-          {this.tabs === 'video' && <VideoList />}
-          {this.tabs === 'music' && <Music />}
-        </div> */}
+        <Popup show={this.fansStatus} class={styles.fansPopup} closeable onClose={() => this.fansStatus = false}>
+          <FansList fansList={this.fansList} onConfirm={(item: any) => {
+            this.chatStatus = true
+            this.chatItem = item
+          }} />
+        </Popup>
+
+        <Popup
+          show={this.chatStatus}
+          position="bottom"
+          round
+          closeable
+          safe-area-inset-bottom
+          onClose={() => (this.chatStatus = false)}
+        >
+          <JoinChat
+            item={this.chatItem}
+            onClose={(id: number) => {
+              this.fansList.forEach((item: any) => {
+                item.id === id && (item.hasWaitAuditFlag = true)
+              })
+              this.chatStatus = false
+            }}
+          />
+        </Popup>
       </div>
     )
   }

+ 217 - 0
src/student/teacher-dependent/teacher-style/index.module.less

@@ -0,0 +1,217 @@
+.elegant {
+  // display: flex;
+  // flex-wrap: wrap;
+  // justify-content: space-between; /* 让元素均匀分布在容器内 */
+  column-count: 2; /* 设置列数 */
+  padding: 14px;
+}
+
+.itemBg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 89;
+}
+
+.tedeoItem {
+  border-radius: 8px;
+  background-color: #fff;
+  overflow: hidden;
+  width: 168px;
+  margin-bottom: 12px;
+  position: relative;
+  display: inline-block;
+  &:first-child {
+    .teCover {
+      height: 170px;
+    }
+  }
+  position: relative;
+  :global {
+    .van-image {
+      height: 100%;
+    }
+  }
+  .iconVideo {
+    position: absolute;
+    top: 5px;
+    right: 8px;
+    width: 24px;
+    height: 24px;
+    z-index: 1;
+  }
+
+  .teCover {
+    height: 140px;
+    width: 100%;
+    vertical-align: middle;
+    overflow: hidden;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .teSection {
+    padding: 8px;
+    
+  }
+
+  .teTitle {
+    font-size: 14px;
+    color: #1a1a1a;
+    line-height: 20px;
+  }
+
+  .info {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .teUserInfo {
+    display: flex;
+    align-items: center;
+    font-size: 12px;
+    color: #999;
+    padding: 2px 0;
+
+    .teUserLogo {
+      width: 18px;
+      height: 18px;
+      border-radius: 50%;
+      margin-right: 5px;
+      overflow: hidden;
+    }
+
+    .teUserName {
+      padding-right: 6px;
+      margin-right: 6px;
+      max-width: 65px;
+      // &::after {
+      //   content: '|';
+      //   display: inline-block;
+      // }
+    }
+  }
+  .teUserNum {
+    color: #999999;
+  }
+}
+
+
+.videoGroup {
+  width: 90%;
+  line-height: 0;
+  overflow: inherit;
+
+  :global {
+    .colVideo {
+      border-radius: var(--van-popup-round-border-radius);
+      overflow: hidden;
+    }
+
+    .video-js {
+      border-radius: var(--van-popup-round-border-radius);
+    }
+  }
+}
+
+.searchContainer {
+  background-color: #fff;
+}
+.item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 14px;
+
+  .searchItem {
+    width: 168px;
+    height: 56px;
+  }
+  .title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #333333;
+    line-height: 20px;
+    padding-top: 10px;
+    padding-left: 75px;
+  }
+  .content {
+    padding-left: 75px;
+    font-size: 12px;
+    color: #666666;
+    line-height: 20px;
+  }
+
+  .searchFollow {
+    background: url('./images/follow_bg.png') no-repeat center;
+    background-size: 100%;
+  }
+  .active {
+    .title,
+    .content {
+      color: #ff1f95;
+    }
+  }
+  .searchLive {
+    background: url('./images/live_bg.png') no-repeat center;
+    background-size: 100%;
+  }
+  .active2 {
+    .title,
+    .content {
+      color: var(--van-primary);
+    }
+  }
+}
+.searchDefault {
+  margin: 0 0 12px 14px;
+  display: inline-flex;
+  align-items: center;
+  padding: 4px 8px;
+  border-radius: 20px;
+  color: #666666;
+  font-size: 13px;
+  background: #f7f8f9;
+  .star {
+    margin-right: 5px;
+  }
+}
+
+.video {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  min-height: 100%;
+  background-color: #ccc;
+  border-radius: 4px 4px 0 0;
+}
+
+.living {
+  position: absolute;
+  top: 0;
+  left: 0;
+  color: #fff;
+  font-weight: 500;
+  right: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  bottom: 0;
+  background: rgba(51, 51, 51, 0.5);
+  span {
+    padding-left: 5px;
+  }
+}
+.animation {
+  height: 18px !important;
+  width: 20px !important;
+}

+ 149 - 0
src/student/teacher-dependent/teacher-style/index.tsx

@@ -0,0 +1,149 @@
+import { defineComponent, reactive } from 'vue'
+import styles from './index.module.less'
+import request from '@/helpers/request'
+import { useRoute } from 'vue-router'
+import ColHeader from '@/components/col-header'
+import ColResult from '@/components/col-result'
+import { List, Popup, Sticky, Image, Icon } from 'vant'
+
+import iconTeacher from '@common/images/icon_teacher.png'
+import iconUploadPoster from '@common/images/icon_upload_poster.png'
+import TheSticky from '@/components/the-sticky'
+import iconVideo from '../images/icon_video.png'
+import ColVideo from '@/components/col-video'
+
+export default defineComponent({
+  name: 'teacher-style',
+  setup() {
+    const route = useRoute()
+    const state = reactive({
+      videoStatus: false,
+      videoItem: {} as any,
+      isAddBrowse: false,
+      dataShow: false,
+      teacherId: route.query.teacherId,
+      list: [] as any,
+      userInfo: {} as any
+    })
+    const getTeacherDetail = async () => {
+      state.dataShow = true
+      try {
+        const { data } = await request.get(
+          '/api-student/teacher/queryTeacherHome',
+          {
+            params: {
+              userId: state.teacherId
+            }
+          }
+        )
+        state.userInfo = data
+        state.list = data.styleVideo || []
+        state.dataShow = state.list.length > 0 ? true : false
+      } catch {}
+    }
+    getTeacherDetail()
+
+    const onPlay = async () => {
+      try {
+        if (!state.isAddBrowse) {
+          return
+        }
+        await request.get('/api-student/teacher/addVideoBrowse', {
+          hideLoading: true,
+          params: {
+            videoId: state.videoItem.id
+          }
+        })
+        state.isAddBrowse = false
+      } catch {}
+    }
+    
+    return () => (
+      <div class={styles['teacher-elegant']}>
+        <TheSticky>
+          <ColHeader
+            border={false}
+            background="transparent"
+            isFixed={false}
+          />
+        </TheSticky>
+
+        {state.dataShow ? (
+          <div class={[styles.elegant]}>
+            {state.list.map((item: any) => (
+              <div class={styles.tedeoItem}>
+                <div
+                  class={styles.itemBg}
+                  onClick={() => {
+                    state.videoStatus = true
+                    state.isAddBrowse = true
+                    state.videoItem = item
+                  }}
+                ></div>
+                <img class={styles.iconVideo} src={iconVideo} />
+                <div class={styles.teCover}>
+                  <Image src={item.cover || iconUploadPoster} fit="cover" />
+                </div>
+                <div class={styles.teSection}>
+                  <div class={styles.info}>
+                    <div class={styles.teUserInfo}>
+                      <Image
+                        src={item.avatar || iconTeacher}
+                        class={styles.teUserLogo}
+                      />
+                      <span
+                        class={[
+                          styles.teUserName,
+                          'van-hairline--right van-ellipsis'
+                        ]}
+                      >
+                        {item?.username || `游客${item?.userId || ''}`}
+                      </span>
+                    </div>
+                    <span class={styles.teUserNum}>{item.browse}浏览</span>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        ) : (
+          <ColResult
+            btnStatus={false}
+            classImgSize="SMALL"
+            tips="暂无老师风采"
+          />
+        )}
+
+        <Popup
+          show={state.videoStatus}
+          round
+          class={styles.videoGroup}
+          closeable
+          onClose={() => {
+            state.videoStatus = false
+            state.isAddBrowse = false
+          }}
+        >
+          {state.videoStatus && (
+            <ColVideo
+              playsinline
+              onPlay={onPlay}
+              src={state.videoItem?.videoUrl}
+            />
+          )}
+
+          {/* <video
+            style={{ height: '210px', width: '100%' }}
+            controls
+            class={styles.video}
+          >
+            <source
+              src={this.videoItem?.videoUrl + '#t=1,4'}
+              type="video/mp4"
+            />
+          </video> */}
+        </Popup>
+      </div>
+    )
+  }
+})

+ 167 - 19
src/student/trade/tradeOrder.ts

@@ -7,18 +7,38 @@ import dayjs from 'dayjs'
 const apiSuffix =
   state.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
 // LIVE: '直播课',
-// PRACTICE: '陪练课',
+// PRACTICE: '趣纠课',
 // VIDEO: '视频课',
 // VIP: '开通会员',
 // MUSIC: '单曲点播'
+
+/** 计算畅学卡时间 */
+export const discountTimer = (discountEndTime: any, period: string) => {
+  const startTime = discountEndTime || new Date()
+  let endTime = dayjs(new Date()).format('YYYY-MM-DD')
+  if (period === 'MONTH') {
+    endTime = dayjs(startTime).add(1, 'month').format('YYYY-MM-DD')
+  } else if (period === 'QUARTERLY') {
+    endTime = dayjs(startTime).add(3, 'month').format('YYYY-MM-DD')
+  } else if (period === 'YEAR_HALF') {
+    endTime = dayjs(startTime).add(6, 'month').format('YYYY-MM-DD')
+  } else if (period === 'YEAR') {
+    endTime = dayjs(startTime).add(1, 'year').format('YYYY-MM-DD')
+  }
+  return {
+    startTime: dayjs(startTime).format('YYYY-MM-DD'),
+    endTime
+  }
+}
+
 interface IAmount {
   couponAmount: number
   discountPrice: number
+  expectPrice: number
 }
 export const formatOrderDetail = async (item: any, amount?: IAmount) => {
   const type = item.goodType
   let tempList: any = {}
-
   switch (type) {
     case 'LIVE':
       {
@@ -53,7 +73,7 @@ export const formatOrderDetail = async (item: any, amount?: IAmount) => {
         }
       }
       break
-    case 'PRACTICE': {
+    case 'PRACTICE': case 'VIP_COURSE': {
       const bizContent: any = JSON.parse(item.bizContent)
       tempList = {
         ...bizContent,
@@ -87,32 +107,113 @@ export const formatOrderDetail = async (item: any, amount?: IAmount) => {
     case 'SVIP':
       {
         try {
-          const res = await getVipDetail(item.id)
-
           // couponAmount 总的优惠金额
           // discountPrice 优惠券金额
-          let tempPrice = res.salePrice || item.actualPrice
-          if (amount?.couponAmount) {
-            tempPrice = Number(((res.salePrice - (amount.couponAmount -
-              amount.discountPrice) / res.times)).toFixed(2))
+          let tempPrice = amount?.expectPrice || item.actualPrice
+          // 如果是赠送活动 就是一口价
+          if (item.activityType !== 'MEMBER') {
+            if (amount?.couponAmount) {
+              tempPrice = Number(
+                (
+                  amount?.expectPrice -
+                  (amount.couponAmount - amount.discountPrice) / item.goodNum
+                ).toFixed(2)
+              )
+            }
+          }
+          // 判断是否有会员
+          let startTime = new Date()
+          if (item.goodType === 'SVIP') {
+            startTime = dayjs(
+              state.user.data.userVip.svipEndDate || new Date()
+            ).toDate()
+          } else if (item.goodType === 'VIP') {
+            // 购买Vip时,先有vip的有效时间,如果没有则取SVIP有效时间,都没有默认当前
+            startTime = dayjs(
+              state.user.data.userVip.vipEndDate ||
+                state.user.data.userVip.svipEndDate ||
+                new Date()
+            ).toDate()
+          }
+          let endTime = new Date()
+          if (item.period === 'MONTH') {
+            endTime = dayjs(startTime)
+              .add(1 * item.goodNum, 'month')
+              .toDate()
+          } else if (item.period === 'QUARTERLY') {
+            endTime = dayjs(startTime)
+              .add(3 * item.goodNum, 'month')
+              .toDate()
+          } else if (item.period === 'YEAR_HALF') {
+            endTime = dayjs(startTime)
+              .add(6 * item.goodNum, 'month')
+              .toDate()
+          } else if (item.period === 'YEAR') {
+            endTime = dayjs(startTime)
+              .add(1 * item.goodNum, 'year')
+              .toDate()
           }
           tempList = {
             orderType: item.goodType,
             goodName: item.goodName,
             num: item.goodNum,
             id: item.id,
-            title: memberType[res.period] || '',
+            title: item.goodName || '',
             vipEndDays: item.vipEndDays,
             // 判断是否有优惠金额
             price: tempPrice,
-            startTime: dayjs(res.startTime).format('YYYY-MM-DD'),
-            endTime: dayjs(res.endTime).format('YYYY-MM-DD')
+            period: item.period,
+            startTime: dayjs(startTime).format('YYYY-MM-DD'),
+            endTime: dayjs(endTime).format('YYYY-MM-DD'),
+            activityList: [], // 活动赠送的东西
+            discountEndTime: state.user.data.discountEndTime, // 畅学卡结束时间
+            discountStartTime: state.user.data.discountStartTime // 畅学卡开始时间
           }
+          tempList.activityList = (item.activityList || []).map(item => {
+            const { goodType, goodName, goodNum, giftFlag, bizId, period } = item
+            if (goodType === 'DISCOUNT') {
+              return {
+                goodType,
+                goodName,
+                goodNum,
+                bizContent: bizId,
+                giftFlag,
+                vipEndDays: null,
+                goodsNum: goodNum,
+                unit:period
+              }
+            }
+            return {
+              goodType,
+              goodName,
+              goodNum,
+              bizContent: bizId,
+              giftFlag,
+              vipEndDays: null,
+              goodsNum: goodNum,
+              unit:period
+            }
+          })
         } catch (e: any) {
           throw new Error(e.message)
         }
       }
       break
+    case 'DISCOUNT':
+      {
+        const users = state.user.data || {}
+        tempList = {
+          orderType: item.goodType,
+          goodName: item.goodName,
+          num: item.goodNum,
+          id: item.id,
+          title: item.goodName || '',
+          // 判断是否有优惠金额
+          price: item.expectPrice,
+          ...discountTimer(users.discountEndTime, item.period)
+        }
+      }
+      break
     case 'MUSIC':
       {
         try {
@@ -129,7 +230,6 @@ export const formatOrderDetail = async (item: any, amount?: IAmount) => {
       break
     case 'ALBUM':
       {
-        console.log(item)
         try {
           const res = await getAlbumDetail(item.bizId)
           tempList = {
@@ -160,7 +260,7 @@ export const formatOrderDetail = async (item: any, amount?: IAmount) => {
   }
   tempList.orderType = type
   tempList.goodName = item.goodName
-  orderStatus.orderObject.orderList.push(tempList)
+  return tempList
 }
 // 获取视频课详情
 export const getVideoDetail = async (groupId: any) => {
@@ -239,8 +339,36 @@ export const getAlbumDetail = async (id: any) => {
   }
 }
 
+/** 处理选择了畅学卡之后的显示 */
+// const formatOrder = (orderDetailList: any, orderType: string) => {
+//   let discountJson: any = {}
+//   orderDetailList.forEach((item: any) => {
+//     if(item.goodType === orderType) {
+//       discountJson = item.discountJson ? JSON.parse(item.discountJson) : {}
+//     }
+//   })
+//   orderDetailList.forEach((item: any) => {
+//     if(item.goodType === "DISCOUNT") {
+//       item.discountPrice = discountJson.DISCOUNT || 0
+//     }
+//   })
+//   return orderDetailList
+// }
+
+const moveToEnd = () => {
+  const orderList = orderStatus.orderObject.orderList || []
+  const index = orderList.findIndex((item: any) => item.orderType === 'DISCOUNT')
+    if (index !== -1) {
+      const i = orderList.splice(index, 1)
+      orderStatus.orderObject.orderList = [
+        ...orderList,
+        ...i
+      ]
+    }
+}
+
 // 为了处理继续支付逻辑
-export const tradeOrder = (result: any, callBack?: any) => {
+export const tradeOrder = async (result: any, callBack?: any) => {
   const {
     orderNo,
     actualPrice,
@@ -249,17 +377,21 @@ export const tradeOrder = (result: any, callBack?: any) => {
     orderType,
     orderDetailList,
     couponAmount, // 优惠金额
+    cardDiscountPrice,
     discountPrice,
     paymentConfig,
     paymentVendor,
-    paymentVersion
+    paymentVersion,
+    expectPrice
   } = result
   orderStatus.orderObject.orderType = orderType
   orderStatus.orderObject.orderName = orderName
   orderStatus.orderObject.orderDesc = orderDesc
   orderStatus.orderObject.actualPrice = actualPrice
   orderStatus.orderObject.orderNo = orderNo
+  orderStatus.orderObject.discountCardPrice = cardDiscountPrice
   orderStatus.orderObject.discountPrice = discountPrice
+  orderStatus.orderObject.couponAmount = couponAmount
   orderStatus.orderObject.paymentConfig = {
     ...paymentConfig,
     paymentVendor,
@@ -267,12 +399,28 @@ export const tradeOrder = (result: any, callBack?: any) => {
   }
   orderStatus.orderObject.orderList = []
   try {
-    orderDetailList.forEach(async (item: any) => {
-      await formatOrderDetail(item, {
+    // 排除活动商品
+    const orderDetails: any[] = []
+    const orderDetailsActivity: any[] = []
+    orderDetailList.map(item => {
+      if (item.giftFlag) {
+        orderDetailsActivity.push(item)
+      } else {
+        orderDetails.push(item)
+      }
+    })
+    orderDetails[0] && (orderDetails[0].activityList = orderDetailsActivity)
+
+    orderDetails.forEach(async (item: any) => {
+      const child = await formatOrderDetail(item, {
         couponAmount,
-        discountPrice
+        discountPrice,
+        expectPrice
       })
+      orderStatus.orderObject.orderList.push(child)
+      moveToEnd()
     })
+
     callBack && callBack()
   } catch {
     //

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

@@ -94,9 +94,6 @@ export default defineComponent({
           isTeacher: result.teacherFlag ? true : false
         }
         this.detailList = result.detailList || []
-
-        console.log(lessonGroup.payType, userInfo.isVip)
-        console.log(this.userInfo, 'this.userInfo')
       } catch {
         //
       }
@@ -131,27 +128,6 @@ export default defineComponent({
         }
 
         const userInfo = this.userInfo
-        orderStatus.orderObject.orderType = 'VIDEO'
-        orderStatus.orderObject.orderName = '视频课购买'
-        orderStatus.orderObject.orderDesc = '视频课购买'
-        orderStatus.orderObject.actualPrice = userInfo.lessonPrice
-        orderStatus.orderObject.recomUserId = this.recomUserId
-        orderStatus.orderObject.orderNo = ''
-        orderStatus.orderObject.orderList = [
-          {
-            orderType: 'VIDEO',
-            goodsName: '视频课购买',
-            courseGroupId: userInfo.id,
-            courseGroupName: userInfo.lessonName,
-            coursePrice: userInfo.lessonPrice,
-            teacherName: userInfo.username || `游客${userInfo.teacherId || ''}`,
-            teacherId: userInfo.teacherId,
-            avatar: userInfo.headUrl,
-            relationType: this.userInfo.relationType,
-            courseInfo: this.detailList,
-            recomUserId: this.recomUserId
-          }
-        ]
 
         // 判断是否是0无订单
         if (userInfo.lessonPrice <= 0) {
@@ -194,6 +170,27 @@ export default defineComponent({
               this.cancelPayment(result.orderNo)
             })
         } else {
+          orderStatus.orderObject.orderType = 'VIDEO'
+          orderStatus.orderObject.orderName = '视频课购买'
+          orderStatus.orderObject.orderDesc = '视频课购买'
+          orderStatus.orderObject.actualPrice = userInfo.lessonPrice
+          orderStatus.orderObject.recomUserId = this.recomUserId
+          orderStatus.orderObject.orderNo = ''
+          orderStatus.orderObject.orderList = [
+            {
+              orderType: 'VIDEO',
+              goodsName: '视频课购买',
+              courseGroupId: userInfo.id,
+              courseGroupName: userInfo.lessonName,
+              coursePrice: userInfo.lessonPrice,
+              teacherName: userInfo.username || `游客${userInfo.teacherId || ''}`,
+              teacherId: userInfo.teacherId,
+              avatar: userInfo.headUrl,
+              relationType: this.userInfo.relationType,
+              courseInfo: this.detailList,
+              recomUserId: this.recomUserId
+            }
+          ]
           this.routerTo()
         }
       } catch {}

二進制
src/styles/font/DIN_Alternate_Bold.ttf


+ 4 - 0
src/styles/font/index.less

@@ -0,0 +1,4 @@
+@font-face {
+  font-family: 'DIN';
+  src: url('./DIN_Alternate_Bold.ttf');
+}

+ 1 - 0
src/styles/index.less

@@ -1,4 +1,5 @@
 @import url('./iconfont/iconfont.css');
+@import url('./font/index.less');
 
 :root {
   // Color Palette

+ 1 - 1
src/teacher/extend-plan/index.tsx

@@ -56,7 +56,7 @@ export default defineComponent({
         page: 1,
         rows: 20
       },
-      // 业务类型:PRACTICE, 陪练课 LIVE, 直播课 VIDEO, 视频课 MUSIC, 乐谱 WITHDRAWAL, 提现 LIVE_SHARE, 直播课分润 VIDEO_SHARE, 视频课分润 MUSIC_SHARE, 乐谱分润 VIP_SHARE, 会员分润 MALL_SHARE, 商品分润 ,可用值:PRACTICE,LIVE,VIDEO,MUSIC,VIP,MALL,WITHDRAWAL,LIVE_SHARE,VIDEO_SHARE,MUSIC_SHARE,VIP_SHARE,MALL_SHARE
+      // 业务类型:PRACTICE, 趣纠课 LIVE, 直播课 VIDEO, 视频课 MUSIC, 乐谱 WITHDRAWAL, 提现 LIVE_SHARE, 直播课分润 VIDEO_SHARE, 视频课分润 MUSIC_SHARE, 乐谱分润 VIP_SHARE, 会员分润 MALL_SHARE, 商品分润 ,可用值:PRACTICE,LIVE,VIDEO,MUSIC,VIP,MALL,WITHDRAWAL,LIVE_SHARE,VIDEO_SHARE,MUSIC_SHARE,VIP_SHARE,MALL_SHARE
       actions: [
         { name: '全部推广', value: '' },
         { name: '直播课', value: 'LIVE_SHARE' },

+ 2 - 2
src/teacher/income-consus/echarts.ts

@@ -53,7 +53,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '陪练课',
+      name: '趣纠课',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -345,7 +345,7 @@ export const pieChartOption = {
       avoidLabelOverlap: false,
       label: { show: false },
       data: [
-        { name: '陪练课', value: '0.00' },
+        { name: '趣纠课', value: '0.00' },
         { name: '直播课', value: '0.00' },
         { name: '视频课', value: '0.00' },
         { name: '乐谱', value: '0.00' },

+ 2 - 2
src/teacher/income-consus/index.tsx

@@ -297,7 +297,7 @@ export default defineComponent({
               <Col span={6}>
                 <i></i>
                 <div class={styles.type}>
-                  <span>陪练课</span>
+                  <span>趣纠课</span>
                   <span class={styles.price}>
                     {moneyFormat(this.moneyInfo.practiceAmount)}
                   </span>
@@ -426,7 +426,7 @@ export default defineComponent({
           <div class={styles.pieData}>
             <div>
               <i class={styles.piePractice}></i>
-              <span class={styles.pieTitle}>陪练课</span>
+              <span class={styles.pieTitle}>趣纠课</span>
               <span>{this.moneyInfo.practiceRate}%</span>
             </div>
             <div>

+ 1 - 1
src/teacher/layout/auth.tsx

@@ -17,7 +17,7 @@ export default defineComponent({
   },
   computed: {
     isNeedView() {
-      return state.user.status === 'login' || this.$route.path === '/login'
+      return state.user.status === 'login' || this.$route.path === '/login' || this.$route.path === '/login-cert'
     }
   },
   mounted() {

二進制
src/teacher/layout/image-cert/bg.png


二進制
src/teacher/layout/image-cert/top.png


+ 99 - 0
src/teacher/layout/login-cert.module.less

@@ -0,0 +1,99 @@
+.login {
+  position: relative;
+  min-height: 100vh;
+  background: url('./image-cert/bg.png') no-repeat top center;
+  background-color: #F5F5F0;
+  background-size: 100%;
+  overflow: hidden;
+
+  .topBg {
+    position: absolute;
+    top: 0;
+    width: 100%
+  }
+
+  .codeText {
+    color: var(--van-primary);
+  }
+
+  .content {
+    margin: 350px 5px 0;
+    position: relative;
+    z-index: 1;
+
+    background: rgba(45,199,170,0.15);
+    border-radius: 20px;
+    border: 2px solid #FFFFFF;
+
+    .cellGroup {
+      margin: 9px;
+      padding: 20px;
+      border-radius: 18px;
+    }
+  }
+
+  .margin34 {
+    margin: 0 34px;
+  }
+
+  .formTitle {
+    font-weight: 500;
+    font-size: 17px;
+    color: #000000;
+    line-height: 24px;
+  }
+
+  :global {
+    .van-field {
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .van-button  {
+      font-size: 18px;
+      color: #FFFFFF;
+    }
+
+    .van-checkbox {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+    }
+
+    .van-checkbox__icon {
+      height: 14px;
+      line-height: 14px;
+      display: inline-block;
+      vertical-align: text-bottom;
+
+      .van-icon__image, .van-icon {
+        width: 14px;
+        height: 14px;
+      }
+    }
+
+    .van-checkbox__label {
+      line-height: 14px;
+      color: #999;
+    }
+  }
+}
+
+.protocol {
+  font-size: 12px;
+  padding: 37px 0 12px;
+  text-align: center;
+  color: #999;
+
+  .protocolText {
+    color: var(--van-primary);
+    line-height: 14px;
+  }
+
+  .boxStyle {
+    background: transparent !important;
+    width: 14px;
+    height: 14px;
+    font-size: 14px;
+    border: transparent !important;
+  }
+}

+ 244 - 0
src/teacher/layout/login-cert.tsx

@@ -0,0 +1,244 @@
+import { defineComponent } from 'vue'
+import { CellGroup, Field, Button, CountDown, Row, Col, Toast, Checkbox, Icon } from 'vant'
+import ImgCode from '@/components/col-img-code'
+import { checkPhone } from '@/helpers/validate'
+import request from '@/helpers/request'
+import { setLogin, state } from '@/state'
+import { removeAuth, setAuth } from '@/helpers/utils'
+import styles from './login-cert.module.less'
+import topBg from './image-cert/top.png'
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+
+type loginType = 'PWD' | 'SMS'
+export default defineComponent({
+  name: 'login',
+  data() {
+    return {
+      checked: false,
+      loginType: 'SMS' as loginType,
+      username: '',
+      password: '',
+      smsCode: '',
+      countDownStatus: true, // 是否发送验证码
+      countDownTime: 1000 * 120, // 倒计时时间
+      countDownRef: null as any, // 倒计时实例
+      imgCodeStatus: false
+    }
+  },
+  computed: {
+    codeDisable() {
+      let status = true
+      if (this.loginType === 'PWD') {
+        this.username && this.password && (status = false)
+      } else {
+        this.username && this.smsCode && this.checked && (status = false)
+      }
+      return status
+    }
+  },
+  mounted() {
+    removeAuth()
+    this.directNext()
+  },
+  methods: {
+    previewProtocol(type: string) {
+      if (type === 'user') {
+        this.$router.push({
+          path: '/registerProtocol',
+          query: {
+            showHeader: 1
+          }
+        })
+      } else if (type === 'privacy') {
+        this.$router.push({
+          path: '/privacyProtocol',
+          query: {
+            showHeader: 1
+          }
+        })
+      }
+    },
+    directNext() {
+      if (state.user.status === 'login' || state.user.status === 'error') {
+        const { returnUrl, isRegister, ...rest } = this.$route.query
+        this.$router.replace({
+          path: '/teacherCert',
+          query: {
+            ...rest
+          }
+        })
+      }
+    },
+    async onLogin() {
+      try {
+        let res: any
+        if (this.loginType === 'PWD') {
+          res = await request.post('/api-auth/usernameLogin', {
+            requestType: 'form',
+            data: {
+              username: this.username,
+              password: this.password,
+              clientId: 'teacher',
+              clientSecret: 'teacher'
+            }
+          })
+        } else {
+          res = await request.post('/api-auth/smsLogin', {
+            requestType: 'form',
+            data: {
+              clientId: 'teacher',
+              clientSecret: 'teacher',
+              phone: this.username,
+              smsCode: this.smsCode
+            }
+          })
+        }
+
+        const { authentication } = res.data
+        setAuth(authentication.token_type + ' ' + authentication.access_token)
+
+        let userCash = await request.get('/api-teacher/teacher/queryUserInfo', {
+          initRequest: true // 初始化接口
+        })
+        setLogin(userCash.data)
+
+        this.directNext()
+      } catch {}
+    },
+    async onSendCode() {
+      // 发送验证码
+      if (!checkPhone(this.username)) {
+        return Toast('请输入正确的手机号码')
+      }
+      this.imgCodeStatus = true
+    },
+    onCodeSend() {
+      this.countDownStatus = false
+      this.countDownRef.start()
+    },
+    onFinished() {
+      this.countDownStatus = true
+      this.countDownRef.reset()
+    },
+    onChange() {
+      if (this.loginType === 'PWD') {
+        this.loginType = 'SMS'
+      } else if (this.loginType === 'SMS') {
+        this.loginType = 'PWD'
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.login}>
+        <img src={topBg} class={styles.topBg} />
+        <div class={styles.content}>
+          <CellGroup class={styles.cellGroup} border={false}>
+            <Row style={{ marginBottom: '16px' }}>
+              <Col span={24} class={styles.formTitle}>
+                手机号
+              </Col>
+              <Col span={24} class="van-hairline--bottom">
+                <Field
+                  v-model={this.username}
+                  name="手机号"
+                  placeholder="请输入您的手机号"
+                  type="tel"
+                  maxlength={11}
+                />
+              </Col>
+            </Row>
+
+            <Row>
+              <Col span={24} class={styles.formTitle}>
+                验证码
+              </Col>
+              <Col span={24} class="van-hairline--bottom">
+                <Field
+                  v-model={this.smsCode}
+                  name="验证码"
+                  placeholder="请输入验证码"
+                  type="tel"
+                  maxlength={6}
+                  // @ts-ignore
+                  vSlots={{
+                    button: () =>
+                      this.countDownStatus ? (
+                        <span class={styles.codeText} onClick={this.onSendCode}>
+                          发送验证码
+                        </span>
+                      ) : (
+                        <CountDown
+                          ref={this.countDownRef}
+                          auto-start={false}
+                          time={this.countDownTime}
+                          onFinish={this.onFinished}
+                          format="ss秒"
+                        />
+                      )
+                  }}
+                />
+              </Col>
+            </Row>
+          </CellGroup>
+        </div>
+        <div class={styles.margin34}>
+          <div class={styles.protocol}>
+            <Checkbox
+              v-model={this.checked}
+              v-slots={{
+                icon: (props: any) => (
+                  <Icon
+                    class={styles.boxStyle}
+                    name={props.checked ? activeButtonIcon : inactiveButtonIcon}
+                    size="15"
+                  />
+                )
+              }}
+            >
+              我已阅读并同意
+            </Checkbox>
+            <span
+              class={styles.protocolText}
+              onClick={() => {
+                this.previewProtocol('user')
+              }}
+            >
+              《用户注册协议》
+            </span>
+            和
+            <span
+              class={styles.protocolText}
+              onClick={() => {
+                this.previewProtocol('privacy')
+              }}
+            >
+              《隐私政策》
+            </span>
+          </div>
+          <Button
+            round
+            block
+            type="primary"
+            disabled={this.codeDisable}
+            onClick={this.onLogin}
+          >
+            确认
+          </Button>
+        </div>
+
+        {this.imgCodeStatus ? (
+          <ImgCode
+            v-model:value={this.imgCodeStatus}
+            phone={this.username}
+            onClose={() => {
+              this.imgCodeStatus = false
+            }}
+            onSendCode={this.onCodeSend}
+          />
+        ) : null}
+      </div>
+    )
+  }
+})

+ 2 - 0
src/teacher/live-class/create-components/arrange.tsx

@@ -62,6 +62,7 @@ export default defineComponent({
               ...params,
               singleCourseMinutes: createState.live.singleMins,
               freeCourseMinutes: createState.live.freeMinutes,
+              courseFreeMinutes: createState.live.freeMinutes,
               teacherId: state.user.data?.userId
             }
           }
@@ -143,6 +144,7 @@ export default defineComponent({
           {
             data: {
               courseNum: createState.live.courseNum,
+              courseFreeMinutes: createState.live.freeMinutes,
               courseType: 'LIVE',
               loop: this.selectType === 'noEnough' ? 1 : 0,
               teacherId: state.user.data?.userId,

+ 2 - 0
src/teacher/piano-room/class-arrangement/create-class/index.tsx

@@ -148,6 +148,7 @@ export default defineComponent({
           {
             data: {
               courseNum: classInfo.classNum,
+              courseFreeMinutes: classInfo.freeCourseMinutes,
               courseType: 'PIANO_ROOM_CLASS',
               loop: 1,
               teacherId: state.user.data?.userId,
@@ -187,6 +188,7 @@ export default defineComponent({
               ), //消耗时长
               courseName: classInfo.courseName, //课程名称
               singleClssTime: classInfo.singleClassTime, //单课时长
+              courseFreeMinutes: classInfo.freeCourseMinutes,
               studentIds: classInfo.studentIds.map(n => n.userId), //学员id集合
               subjectId: classInfo.subjectId, //声部id
               timeList

+ 1 - 1
src/teacher/piano-room/tradeOrder.ts

@@ -3,7 +3,7 @@ import request from '@/helpers/request'
 import { orderStatus } from '@/views/order-detail/orderStatus'
 import dayjs from 'dayjs'
 // LIVE: '直播课',
-// PRACTICE: '陪练课',
+// PRACTICE: '趣纠课',
 // VIDEO: '视频课',
 // VIP: '开通会员',
 // MUSIC: '单曲点播'

二進制
src/teacher/practice-class/icon-question.png


+ 1 - 1
src/teacher/practice-class/model/timer.tsx

@@ -172,7 +172,7 @@ export default defineComponent({
         <div class={styles.tips}>
           <div class={styles.tipsTitle}>请选择陪练开始时间</div>
           <div class={styles.tipsTime}>
-            陪练课单课时时长为 <span>{this.courseMinutes}</span> 分钟
+            趣纠课单课时时长为 <span>{this.courseMinutes}</span> 分钟
           </div>
         </div>
 

+ 38 - 0
src/teacher/practice-class/practice-setting.module.less

@@ -13,6 +13,36 @@
   }
 }
 
+.showPrice {
+  background: #FFFAEE;
+  border-radius: 4px;
+  border: 1px solid;
+  border-image: linear-gradient(180deg, rgba(250, 237, 215, 1), rgba(251, 233, 206, 1)) 1 1;
+  padding: 9px 6px;
+  line-height: 1;
+  font-size: 13px;
+  color: #F68E3E;
+  margin-bottom: 12px;
+  span {
+    font-weight: 600;
+    font-size: 13px;
+    color: #FF4E1A;
+  }
+}
+
+.tipsAccount {
+  padding-top: 12px;
+  font-size: 14px;
+color: #999999;
+line-height: 24px;
+padding: 0 14px;
+span {
+  color: #FF4E19;
+  font-weight: bold;
+  padding: 0 4px;
+}
+}
+
 .radio-group,
 .checkbox-group {
   display: flex;
@@ -77,3 +107,11 @@
     }
   }
 }
+
+.PracticeSettingBtns {
+  :global {
+    .van-button {
+      font-size: 18px;
+    }
+  }
+}

+ 98 - 170
src/teacher/practice-class/practice-setting.tsx

@@ -6,8 +6,6 @@ import request from '@/helpers/request'
 import { postMessage } from '@/helpers/native-message'
 import {
   Form,
-  Radio,
-  RadioGroup,
   Tag,
   Sticky,
   Button,
@@ -21,14 +19,25 @@ import {
 import { defineComponent } from 'vue'
 import styles from './practice-setting.module.less'
 import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
-import Timer from './model/timer'
 import ColHeader from '@/components/col-header'
 import { state } from '@/state'
+import TheSticky from '@/components/the-sticky'
+import { moneyFormat } from '@/helpers/utils'
+
+/** 保留两位小数向上取整 */
+export const numberToTwoUp = (num: number | string) => {
+  num = Number(num)
+
+  return Math.ceil(num * 100) / 100
+}
 
 export default defineComponent({
   name: 'PracticeSetting',
   data() {
+    const query = this.$route.query
     return {
+      courseType: query.tabs == 'vip' ? 'VIP' : 'PRACTICE',
+      text: query.tabs == 'vip' ? 'VIP定制课' : '趣纠课',
       subjectList: [],
       chargeTypeArr: {
         0: '否',
@@ -37,26 +46,19 @@ export default defineComponent({
       checkStatus:false, // 是否审核版本
       classTimeStatus: false,
       subjectStatus: false,
-      timerStatus: false,
-      timeSetting: {
-        courseMinutes: 25,
-        freeMinutes: 5,
-        startSetting: '08:00',
-        endSetting: '18:00'
-      },
-      timerObject: {} as any,
       form: {
-        enableFlag: 1,
+        lowestPrice: 0 as any,
+        highestPrice: 0 as any,
+        coursePrice: 0 as any,
         courseMinutes: null as any,
         freeMinutes: 0,
         subjectIdTemp: '',
         subjectId: [] as any[],
         subjectPrice: [] as any[],
-        skipHolidayFlag: 1,
-        setting: ''
       },
       minutes: [] as any,
-      rate: 0
+      rate: 0,
+      accountPeriod: 0, // 账期
     }
   },
   computed: {
@@ -67,6 +69,7 @@ export default defineComponent({
     }
   },
   async mounted() {
+    document.title = `${this.text}设置`
     try {
       // 获取手续费和分钟数
       const config = await request.get(
@@ -74,13 +77,13 @@ export default defineComponent({
         {
           params: {
             paramNames:
-              'practice_times_setting,practice_service_fee,course_start_setting,course_end_setting'
+              'practice_times_setting,practice_service_fee,vip_course_times_setting,vip_course_service_fee,vip_course_account_period,practice_account_period'
           }
         }
       )
       const configData = config.data || []
       configData.forEach((item: any) => {
-        if (item.paramName === 'practice_times_setting') {
+        if (item.paramName === (this.courseType === 'VIP' ? 'vip_course_times_setting' : 'practice_times_setting')) {
           const mins = item.paramValue ? JSON.parse(item.paramValue) : []
           const tempArr = [] as any
           mins.forEach((item: any) => {
@@ -90,15 +93,20 @@ export default defineComponent({
             })
           })
           this.minutes = [...tempArr]
+          if(this.minutes.length > 0) {
+            const minute = this.minutes[0]
+            this.form.coursePrice = minute.price
+            this.form.courseMinutes = minute.courseMinutes
+            this.form.freeMinutes = minute.freeMinutes
+            this.form.lowestPrice = minute.lowestPrice || 0
+            this.form.highestPrice = minute.highestPrice || 0
+          }
         }
-        if (item.paramName === 'practice_service_fee') {
+        if (item.paramName === (this.courseType === 'VIP' ? 'vip_course_service_fee' : 'practice_service_fee')) {
           this.rate = item.paramValue
         }
-        if (item.paramName === 'course_start_setting') {
-          this.timeSetting.startSetting = item.paramValue
-        }
-        if (item.paramName === 'course_end_setting') {
-          this.timeSetting.endSetting = item.paramValue
+        if (item.paramName === (this.courseType === 'VIP' ? 'vip_course_account_period' : 'practice_account_period')) {
+          this.accountPeriod = item.paramValue
         }
       })
       //
@@ -107,60 +115,30 @@ export default defineComponent({
 
       // 获取课程设置
       const setting = await request.post(
-        '/api-teacher/teacherFreeTime/getDetail',
+        '/api-teacher/teacherSubjectPrice/list',
         {
           data: {
-            defaultFlag: 1
+            courseType: this.courseType
           }
         }
       )
       const sr = setting.data
-      if (sr) {
-        this.timeSetting.courseMinutes = sr.courseMinutes
-        this.timeSetting.freeMinutes = sr.freeMinutes
-
-        this.timerObject = {
-          monday: sr.monday ? JSON.parse(sr.monday) : [],
-          tuesday: sr.tuesday ? JSON.parse(sr.tuesday) : [],
-          wednesday: sr.wednesday ? JSON.parse(sr.wednesday) : [],
-          thursday: sr.thursday ? JSON.parse(sr.thursday) : [],
-          friday: sr.friday ? JSON.parse(sr.friday) : [],
-          saturday: sr.saturday ? JSON.parse(sr.saturday) : [],
-          sunday: sr.sunday ? JSON.parse(sr.sunday) : []
-        }
-
-        const tempIds: any = []
-        const tempPrices: any = []
-        const subjectPrice = sr.subjectPrice || []
-        subjectPrice.forEach((item: any) => {
-          tempIds.push(item.subjectId)
-          tempPrices.push({
+      if(Array.isArray(sr)) {
+        const tempSubjects: any = []
+        sr.forEach((item: any) => {
+          this.form.courseMinutes = item.courseMinutes
+          this.form.freeMinutes = item.freeMinutes
+          this.form.subjectId.push(item.subjectId)
+          tempSubjects.push({
             subjectId: item.subjectId,
-            subjectPrice: item.subjectPrice,
-            subjectName: item.subjectName
+            subjectName: item.subjectName,
+            subjectPrice: item.subjectPrice
           })
         })
-        const to = this.timerObject
-        this.form = {
-          enableFlag: sr.enableFlag,
-          courseMinutes: sr.courseMinutes,
-          freeMinutes: sr.freeMinutes,
-          subjectIdTemp: tempIds.join(','),
-          subjectId: tempIds,
-          subjectPrice: tempPrices,
-          skipHolidayFlag: sr.skipHolidayFlag,
-          setting:
-            to.monday.length > 0 ||
-            to.tuesday.length > 0 ||
-            to.wednesday.length > 0 ||
-            to.thursday.length > 0 ||
-            to.friday.length > 0 ||
-            to.saturday.length > 0 ||
-            to.sunday.length > 0
-              ? '已设置'
-              : ''
-        }
+        this.form.subjectIdTemp = this.form.subjectId.join(',')
+        this.form.subjectPrice = tempSubjects
       }
+      
 
       // 判断如果是审核的则不显示
       const resVersion = await request.post('/api-teacher/open/appVersion', {
@@ -175,32 +153,15 @@ export default defineComponent({
     } catch {}
   },
   methods: {
+    onCalcCoursePrice(price: number | string) {
+      if(!price) { return '-' }
+      const money = Number(price)
+      return moneyFormat(numberToTwoUp(this.rate * money / 100))
+    },
     onSelect(item: any) {
-      // 如果分钟数不同,则清空
-      if (this.form.courseMinutes !== item.courseMinutes) {
-        this.timerObject = {}
-        this.form.setting = ''
-      }
       this.form.courseMinutes = item.courseMinutes
       this.form.freeMinutes = item.freeMinutes
     },
-    async onTimer() {
-      try {
-        const form = this.form
-        if (!form.courseMinutes) {
-          Toast('请选择单课时时长')
-          return
-        }
-        this.timeSetting.courseMinutes = Number(form.courseMinutes)
-        this.timeSetting.freeMinutes = Number(form.freeMinutes)
-        this.timerStatus = true
-      } catch {}
-    },
-    onChoiceTimer(item: any, status: boolean) {
-      this.form.setting = status ? '已设置' : ''
-      this.timerObject = item
-      this.timerStatus = false
-    },
     onChoice(item: any) {
       console.log(item)
       const tempItem = item || []
@@ -212,9 +173,10 @@ export default defineComponent({
           (subject: any) => subject.subjectId === item
         )
         if (index === -1) {
+          console.log(this.form, 'this.orm')
           subjectPriceList.push({
             subjectId: item,
-            subjectPrice: this.checkStatus ? 0 : null as any,
+            subjectPrice: this.checkStatus ? 0 : this.form.coursePrice,
             subjectName: ''
           })
         }
@@ -234,22 +196,39 @@ export default defineComponent({
       const subject: any = this.subjectList.find((item: any) => item.id === id)
       return subject ? subject.name : ''
     },
+    validatePrice(rule?: any) {
+      if(Number(rule) > Number(this.form.highestPrice) || Number(rule) < Number(this.form.lowestPrice)) {
+        return false
+      } else {
+        return true
+      }
+    },
     onFormatter(val: any) {
       return verifyNumberIntegerAndFloat(val)
     },
     async onSubmit() {
       try {
         const form = this.form
+        const params: any = []
         form.subjectPrice.forEach((item: any) => {
-          item.subjectName = this.getSubjectName(item.subjectId)
+          const subjectName = this.getSubjectName(item.subjectId)
+          console.log(subjectName, 'subjectName')
+          params.push({
+            ...item,
+            courseMinutes: this.form.courseMinutes,
+            courseType: this.courseType,
+            freeMinutes: this.form.freeMinutes,
+            subjectName
+            
+          })
         })
-        await request.post('/api-teacher/teacherFreeTime/upSet', {
-          data: {
-            ...form,
-            ...this.timerObject
-          }
+        console.log(params,'foparamsrm')
+        await request.post('/api-teacher/teacherSubjectPrice/saveOrUpdate', {
+          data: params
         })
-        Toast('设置成功')
+        setTimeout(() => {
+          Toast('设置成功')
+        }, 100);
         setTimeout(() => {
           postMessage({ api: 'back', content: {} })
         }, 500)
@@ -259,27 +238,8 @@ export default defineComponent({
   render() {
     return (
       <Form style={{ paddingTop: '15px' }} onSubmit={this.onSubmit}>
-        <ColHeader />
+        <ColHeader title={`${this.text}设置`} />
         <ColFieldGroup>
-          <ColField title="是否开启陪练课" required border={false}>
-            <RadioGroup
-              class={styles['radio-group']}
-              modelValue={this.form.enableFlag}
-              onUpdate:modelValue={val => (this.form.enableFlag = val)}
-            >
-              {['1', '0'].map((item: string) => {
-                const isActive = Number(item) === Number(this.form.enableFlag)
-                const type = isActive ? 'primary' : 'default'
-                return (
-                  <Radio class={styles.radio} name={item}>
-                    <Tag size="large" plain={isActive} type={type}>
-                      {this.chargeTypeArr[item]}
-                    </Tag>
-                  </Radio>
-                )
-              })}
-            </RadioGroup>
-          </ColField>
           <ColField title="可教授乐器" required>
             {this.form.subjectPrice && this.form.subjectPrice.length > 0 && (
               <CheckboxGroup
@@ -371,7 +331,7 @@ export default defineComponent({
           <ColFieldGroup>
             {this.form.subjectPrice.map((item: any) => (
               <ColField
-                title={`${this.getSubjectName(item.subjectId)}声部陪练价格`}
+                title={`${this.getSubjectName(item.subjectId)}声部${this.text}价格`}
                 required
               >
                 <Field
@@ -379,68 +339,46 @@ export default defineComponent({
                   name="singleMins"
                   type="number"
                   labelWidth={'auto'}
+                  readonly={this.courseType === 'PRACTICE' ? true : false}
                   label={`${this.form.courseMinutes || 0}分钟 / `}
                   rules={[
                     {
                       required: true,
-                      message: `请选择声部陪练价格`
-                    }
+                      message: `请输入${this.getSubjectName(item.subjectId)}声部${this.text}价格`
+                    },
+                    this.courseType === "VIP" ? {
+                      validator: this.validatePrice,
+                      message: `请输入价格在${this.form.lowestPrice}~${this.form.highestPrice}范围内`
+                    } : {} as any
                   ]}
                   formatter={this.onFormatter}
                   maxlength={8}
-                  placeholder={`请选择声部陪练价格`}
+                  placeholder={this.courseType === 'VIP' ? `${this.form.lowestPrice}~${this.form.highestPrice}` :`请选择声部${this.text}价格`}
                   v-slots={{
                     button: () => <span>元</span>
                   }}
                 />
+                <div class={styles.showPrice}>
+                课程预计收入:<span>¥ {this.onCalcCoursePrice(item.subjectPrice)}</span>
+              </div>
               </ColField>
             ))}
           </ColFieldGroup>
         )}
 
-        <ColFieldGroup>
-          <ColField title="可陪练时间段">
-            <Field
-              modelValue={this.form.setting}
-              name="singleMins"
-              readonly
-              isLink
-              onClick={this.onTimer}
-              placeholder="未设置"
-            />
-          </ColField>
-        </ColFieldGroup>
-
-        <ColFieldGroup>
-          <ColField required title="是否跳过节假日" border={false}>
-            <RadioGroup
-              class={styles['radio-group']}
-              modelValue={this.form.skipHolidayFlag}
-              onUpdate:modelValue={val => (this.form.skipHolidayFlag = val)}
-            >
-              {['1', '0'].map((item: string) => {
-                const isActive =
-                  Number(item) === Number(this.form.skipHolidayFlag)
-                const type = isActive ? 'primary' : 'default'
-                return (
-                  <Radio class={styles.radio} name={item}>
-                    <Tag size="large" plain={isActive} type={type}>
-                      {this.chargeTypeArr[item]}
-                    </Tag>
-                  </Radio>
-                )
-              })}
-            </RadioGroup>
-          </ColField>
-        </ColFieldGroup>
+        <div class={styles.tipsAccount}>
+          扣除应缴税金和平台服务费后 <br />
+          实际课程收入按学生实际付款金额计算<br />
+          您的课程收入将在课程结束<span>{this.accountPeriod}</span>天后结算到您的账户
+        </div>
 
-        <Sticky offsetBottom={0} position="bottom">
-          <div class={'btnGroup'}>
+        <TheSticky position="bottom">
+          <div class={['btnGroup', styles.PracticeSettingBtns]}>
             <Button block round type="primary" native-type="submit">
               提交
             </Button>
           </div>
-        </Sticky>
+        </TheSticky>
 
         <ColPopup v-model={this.subjectStatus} destroy>
           <ColHeader />
@@ -453,17 +391,7 @@ export default defineComponent({
           />
         </ColPopup>
 
-        <ColPopup v-model={this.timerStatus} destroy>
-          <ColHeader title="设置陪练时间段" />
-          <Timer
-            onChoice={this.onChoiceTimer}
-            timerObject={this.timerObject}
-            courseMinutes={Number(this.timeSetting.courseMinutes)}
-            freeMinutes={Number(this.timeSetting.freeMinutes)}
-            startSetting={this.timeSetting.startSetting}
-            endSetting={this.timeSetting.endSetting}
-          />
-        </ColPopup>
+
 
         <ActionSheet
           v-model:show={this.classTimeStatus}

+ 140 - 0
src/teacher/practice-class/timer/timer.module.less

@@ -0,0 +1,140 @@
+.timer {
+  background: #f6f8f9;
+  min-height: 100vh;
+  overflow: hidden;
+  padding: 0 14px;
+  :global {
+    .van-button {
+      font-size: 18px;
+      color: #FFFFFF;
+    }
+  }
+}
+.tips {
+  margin: 12px 0;
+  padding: 15px 12px;
+  background: #ffffff;
+  border-radius: 10px;
+
+  .tipsTitle {
+    font-size: 18px;
+    font-weight: 500;
+    color: #000000;
+    line-height: 25px;
+  }
+
+  .tipsTime {
+    padding-top: 4px;
+    font-size: 14px;
+    color: #ff9e5a;
+    line-height: 22px;
+    span {
+      font-weight: 600;
+    }
+  }
+}
+
+.radio-group,
+.checkbox-group {
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 14px;
+  .radio:first-child {
+    :global {
+      .van-radio__label {
+        margin-left: 0;
+      }
+    }
+  }
+  .checkbox:first-child {
+    :global {
+      .van-checked__label {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+.radio {
+  :global {
+    .van-radio__icon {
+      display: none;
+    }
+  }
+}
+.colField {
+  margin: 12px 0;
+  padding: 14px 12px;
+  background-color: #fff;
+  border-radius: 10px;
+  .radio-group {
+    :global {
+      .van-tag--large {
+        width: 94px;
+        height: 30px;
+        font-size: 16px;
+        text-align: center;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+      .van-tag {
+        box-sizing: border-box;
+      }
+      .van-tag--default {
+        color: var(--van-tag-text-default-color);
+      }
+      .van-tag--primary {
+        background-color: var(--tag-bg-color);
+      }
+    }
+  }
+
+  .title {
+    display: flex;
+    align-items: center;
+    img {
+      width: 18px;
+      height: 18px;
+      margin-right: 4px;
+    }
+  }
+}
+.timerContainer {
+  background: #ffffff;
+  border-radius: 10px;
+  padding: 14px 5px 9px;
+  :global {
+    .van-col {
+      margin-bottom: 5px;
+    }
+  }
+}
+
+.tag {
+  height: 28px;
+  background: #eff6f5;
+  border-radius: 4px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #2dc7aa;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.active {
+  background: #2dc7aa;
+  color: #ffffff;
+}
+
+.select {
+  color: #ffffff !important;
+  background: #ffb752;
+}
+
+
+.btnGroupTimer {
+  padding-top: 20px;
+  background: #F6F8F9;
+}

+ 310 - 0
src/teacher/practice-class/timer/timer.tsx

@@ -0,0 +1,310 @@
+import { Button, Col, Radio, RadioGroup, Row, Tag, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './timer.module.less'
+import dayjs from 'dayjs'
+import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
+import customParseFormat from 'dayjs/plugin/customParseFormat'
+import iconQuestion from '../icon-question.png'
+import ColField from '@/components/col-field'
+import request from '@/helpers/request'
+import { postMessage } from '@/helpers/native-message'
+import TheSticky from '@/components/the-sticky'
+import ColHeader from '@/components/col-header'
+dayjs.extend(customParseFormat)
+dayjs.extend(isSameOrBefore)
+
+export default defineComponent({
+  name: 'timer',
+  data() {
+    return {
+      startSetting: '08:00',  // 开始设置时间
+      endSetting: '18:00', // 结束设置时间
+      // freeMinutes: 5, // 空余时长
+      courseMinutes: 30, // // 课程时长
+      timerObject: {},
+      chargeTypeArr: {
+        0: '否',
+        1: '是'
+      },
+      timerList: [],
+      skipHolidayFlag: 1,
+      list: [] as any,
+      weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
+      weekType: [
+        'monday',
+        'tuesday',
+        'wednesday',
+        'thursday',
+        'friday',
+        'saturday',
+        'sunday'
+      ]
+    }
+  },
+  async mounted() {
+   await this._initFetch()
+    this.list = this.timerInit(
+      this.startSetting,
+      this.endSetting,
+      this.courseMinutes || 30
+    )
+  },
+  methods: {
+    async _initFetch() {
+      try {
+        // 获取手续费和分钟数
+        const { data } = await request.get(
+          '/api-teacher/teacher/queryTeacherTime'
+        )
+        this.startSetting = data.startTime ? data.startTime: this.startSetting
+        this.endSetting = data.endTime ? data.endTime: this.endSetting
+        this.courseMinutes = data.intervalTime  ? data.intervalTime: this.courseMinutes
+        
+        // const configData = config.data || []
+        // configData.forEach((item: any) => {
+        //   if (item.paramName === 'course_start_setting') {
+        //     this.startSetting = item.paramValue
+        //   }
+        //   if (item.paramName === 'course_end_setting') {
+        //     this.endSetting = item.paramValue
+        //   }
+        // })
+  
+        // 获取课程设置
+        const setting = await request.get('/api-teacher/teacherFreeTime/get')
+        const sr = setting.data
+        if (sr) {
+          this.timerObject = {
+            monday: sr.monday ? JSON.parse(sr.monday) : [],
+            tuesday: sr.tuesday ? JSON.parse(sr.tuesday) : [],
+            wednesday: sr.wednesday ? JSON.parse(sr.wednesday) : [],
+            thursday: sr.thursday ? JSON.parse(sr.thursday) : [],
+            friday: sr.friday ? JSON.parse(sr.friday) : [],
+            saturday: sr.saturday ? JSON.parse(sr.saturday) : [],
+            sunday: sr.sunday ? JSON.parse(sr.sunday) : []
+          }
+          this.skipHolidayFlag = sr.skipHolidayFlag ? 1 : 0
+        }
+  
+        
+      } catch {}
+    },
+    timerInit(startTime: string, endTime: string, space: number) {
+      let start = dayjs(startTime, 'HH:mm')
+      const end = dayjs(endTime, 'HH:mm')
+
+      const timerList: any = []
+      // 生成一天的时间段
+      while (start.add(space, 'minute').isSameOrBefore(dayjs(end))) {
+        const item = {
+          startTime: start.format('HH:mm'),
+          endTime: start.add(space, 'minute').format('HH:mm'),
+          status: false
+        }
+        // 一周
+        timerList.push(item)
+        start = start.add(space, 'minute')
+      }
+      const list: any = []
+      // 生成一周的时间段
+      timerList.forEach((item: any) => {
+        const weekList: any = []
+        for (let i = 0; i < 7; i++) {
+          weekList.push({
+            ...item
+          })
+        }
+        list.push(weekList)
+      })
+
+      const tempList = this._initData(list)
+      return tempList
+    },
+    _initData(list: Array<any>) {
+      // 回显数据
+      const weekType = this.weekType
+      const timerObject = this.timerObject
+      list.forEach((item: any) => {
+        item.forEach((slot: any, slotIndex: number) => {
+          const dayList = timerObject[weekType[slotIndex]]
+          const startTime = dayjs(slot.startTime, 'HH:mm').format('HH:mm:ss')
+          const isExist = dayList?.some(
+            (course: any) => course.startTime === startTime
+          )
+          isExist && (slot.status = true)
+        })
+      })
+      return list
+    },
+    btnStatus(index: number, type: 'row' | 'col') {
+      if (type === 'row') {
+        return this.list.every((item: any) => {
+          return item[index].status
+        })
+      }
+
+      if (type == 'col') {
+        return this.list[index].every((item: any) => item.status)
+      }
+    },
+    choice(index: number, type: 'row' | 'col', status?: boolean) {
+      if (type === 'row') {
+        this.list.forEach((item: any, i: number) => {
+          const type = !status ? true : false
+          item[index].status = type
+        })
+      }
+
+      if (type == 'col') {
+        this.list[index].forEach((item: any, i: number) => {
+          const type = !status ? true : false
+          item.status = type
+        })
+      }
+    },
+    async onSubmit() {
+      const list = this.list
+      const weekList = {
+        monday: [],
+        tuesday: [],
+        wednesday: [],
+        thursday: [],
+        friday: [],
+        saturday: [],
+        sunday: []
+      }
+      const weekType = this.weekType
+      let status = false
+      list.forEach((item: any, i: number) => {
+        item.forEach((times: any, j: number) => {
+          if (times.status) {
+            status = true
+            weekList[weekType[j]].push({
+              startTime: dayjs(times.startTime, 'HH:mm').format('HH:mm:ss'),
+              endTime: dayjs(times.endTime, 'HH:mm').format('HH:mm:ss')
+            })
+          }
+        })
+      })
+      await request.post('/api-teacher/teacherFreeTime/upSet', {
+        data: {...weekList,
+          skipHolidayFlag: this.skipHolidayFlag}
+      })
+      setTimeout(() => {
+        Toast('设置成功')
+      }, 100);
+      setTimeout(() => {
+        postMessage({ api: 'back', content: {} })
+      }, 500)
+    }
+  },
+  render() {
+    return (
+      <div class={styles.timer}>
+        <ColHeader  />
+
+        <ColField  border={false} class={styles.colField}>
+          {{
+            title: () => <div class={styles.title}>
+              <img src={iconQuestion}></img>
+              是否跳过节假日
+            </div>,
+            default: () => <RadioGroup
+            class={styles['radio-group']}
+            modelValue={this.skipHolidayFlag}
+            onUpdate:modelValue={val => (this.skipHolidayFlag = val)}
+          >
+            {['1', '0'].map((item: string) => {
+              const isActive =
+                Number(item) === Number(this.skipHolidayFlag)
+              const type = isActive ? 'primary' : 'default'
+              return (
+                <Radio class={styles.radio} name={item}>
+                  <Tag size="large" plain={isActive} type={type}>
+                    {this.chargeTypeArr[item]}
+                  </Tag>
+                </Radio>
+              )
+            })}
+          </RadioGroup>
+          }}
+        </ColField>
+        {/* <div class={styles.tips}>
+          <div class={styles.tipsTitle}>请选择陪练开始时间</div>
+          <div class={styles.tipsTime}>
+            趣纠课单课时时长为 <span>{this.courseMinutes}</span> 分钟
+          </div>
+        </div> */}
+
+        <div class={[styles.timerContainer, 'mb12']}>
+          <Row gutter={5}>
+            <Col span={3}></Col>
+            {this.weekList.map((item: any) => (
+              <Col span={3}>
+                <span class={styles.tag}>{item}</span>
+              </Col>
+            ))}
+          </Row>
+
+          <Row gutter={5}>
+            <Col span={3}></Col>
+            {this.weekList.map((item: any, index: number) => (
+              <Col span={3}>
+                <span
+                  class={[
+                    styles.tag,
+                    this.btnStatus(index, 'row') && styles.active
+                  ]}
+                  onClick={() =>
+                    this.choice(index, 'row', this.btnStatus(index, 'row'))
+                  }
+                  title={item}
+                >
+                  全选
+                </span>
+              </Col>
+            ))}
+          </Row>
+
+          {this.list.map((item: any, index: number) => (
+            <Row gutter={5}>
+              <Col span={3}>
+                <span
+                  class={[
+                    styles.tag,
+                    this.btnStatus(index, 'col') && styles.active
+                  ]}
+                  onClick={() =>
+                    this.choice(index, 'col', this.btnStatus(index, 'col'))
+                  }
+                >
+                  全选
+                </span>
+              </Col>
+              {item.map((week: any) => (
+                <Col span={3}>
+                  <span
+                    class={[styles.tag, week.status && styles.select]}
+                    title={week}
+                    style={{ color: '#333333' }}
+                    onClick={() => (week.status = !week.status)}
+                  >
+                    {week.startTime}
+                  </span>
+                </Col>
+              ))}
+            </Row>
+          ))}
+        </div>
+
+        <TheSticky position='bottom' >
+          <div class={['btnGroup', styles.btnGroupTimer]}>
+            <Button block round type="primary" onClick={this.onSubmit}>
+              确定
+            </Button>
+          </div>
+        </TheSticky>
+      </div>
+    )
+  }
+})

+ 2 - 2
src/tenant/exercise-record/echats.ts

@@ -227,7 +227,7 @@ export const lineChartOption = {
 //         '0.00'
 //       ],
 //       symbol: 'circle',
-//       name: '陪练课',
+//       name: '趣纠课',
 //       type: 'line',
 //       emphasis: { lineStyle: { width: 1 } }
 //     },
@@ -519,7 +519,7 @@ export const pieChartOption = {
       avoidLabelOverlap: false,
       label: { show: false },
       data: [
-        { name: '陪练课', value: '0.00' },
+        { name: '趣纠课', value: '0.00' },
         { name: '直播课', value: '0.00' },
         { name: '视频课', value: '0.00' },
         { name: '乐谱', value: '0.00' },

+ 1 - 1
src/tenant/trade/tradeOrder.ts

@@ -7,7 +7,7 @@ import dayjs from 'dayjs'
 const apiSuffix =
   state.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
 // LIVE: '直播课',
-// PRACTICE: '陪练课',
+// PRACTICE: '趣纠课',
 // VIDEO: '视频课',
 // VIP: '开通会员',
 // MUSIC: '单曲点播'

+ 1 - 0
src/views/adapay/payment/index.tsx

@@ -131,6 +131,7 @@ export default defineComponent({
       emit('backOut')
     }
     const onSubmit = async () => {
+      console.log(props.paymentVendor,'props.paymentVendor')
       if (
         props.paymentVendor &&
         (props.paymentVendor.indexOf('wxpay') > -1 ||

+ 56 - 3
src/views/member-center/index.module.less

@@ -195,6 +195,22 @@
   border-radius: 16px 16px 0px 0px;
   padding-bottom: 10px;
 }
+.activitTip2{
+  margin: 16px;
+  background: linear-gradient( 90deg, #FFE9EE 0%, #FFECD9 100%);
+  border-radius: 8px;
+  padding: 8px 9px;
+  .activitTipText{
+    white-space: pre-line;
+    font-weight: 500;
+    font-size: 14px;
+    color: #6B4429;
+    line-height: 22px;
+    background: linear-gradient(90deg, #F31234 0%, #FF6E00 100%);
+    -webkit-background-clip: text; /* 让背景裁剪到文字上 */
+    -webkit-text-fill-color: transparent; /* 文字填充透明色,显示背景渐变 */
+  }
+}
 
 .member_tabs {
   display: flex;
@@ -206,11 +222,32 @@
     text-align: center;
     padding: 12px 0;
     cursor: pointer;
-    opacity: 0.3;
+    position: relative;
+    & > div{
+      opacity: 0.3;
+    }
+    .activitTip{
+      background: linear-gradient( 315deg, #FF1E3D 0%, #FF0704 51%, #FF7A4F 100%);
+      border-radius: 11px 11px 11px 0px;
+      font-weight: 600;
+      font-size: 12px;
+      color: #FFFFFF;
+      line-height: 18px;
+      padding: 1px 6px;
+      white-space: nowrap;
+      position: absolute;
+      left: 50%;
+      top: 0;
+      transform: translateY(-50%);
+      opacity: 1;
+      font-family: Helvetica Neue, Helvetica, PingFangSC;
+    }
   }
 
   .member_tab_active {
-    opacity: 1;
+    & > div{
+      opacity: 1;
+    }
   }
 
   .icon_member {
@@ -458,6 +495,20 @@
     background-size: cover;
   }
 
+  .activitTip1{
+    position: absolute;
+    background: linear-gradient( 315deg, #FF3C83 0%, #FF0704 51%, #FF7A4F 100%);
+    border-radius: 6px 6px 6px 0px;
+    font-weight: 600;
+    font-size: 12px;
+    color: #FFFFFF;
+    line-height: 17px;
+    padding: 1px 6px 2px;
+    left: -1px;
+    top: -9px;
+    font-family: Helvetica Neue, Helvetica, PingFangSC;
+  }
+
   .s_title {
     position: relative;
     font-weight: 500;
@@ -520,7 +571,9 @@
     .iconPermanent {
       left: -2px;
     }
-
+    .activitTip1 {
+      left: -2px;
+    }
     .extraTip {
       background: #0ED8B0;
       color: #FFFFFF;

+ 162 - 28
src/views/member-center/index.tsx

@@ -23,7 +23,7 @@ import ColSticky from '@/components/col-sticky'
 import { moneyFormat } from '@/helpers/utils'
 import { useRoute, useRouter } from 'vue-router'
 import deepClone from '@/helpers/deep-clone'
-import { memberSimpleType, memberType } from '@/constant'
+import { memberSimpleType, memberType, studyCardType } from '@/constant'
 import dayjs from 'dayjs'
 import { orderStatus } from '../order-detail/orderStatus'
 import TheNoticeBar from '@/components/the-noticeBar'
@@ -58,6 +58,20 @@ export default defineComponent({
       memberShowList: [] // 购买商品信息
     })
 
+    // 活动数据
+    const activitData = reactive({
+      activityId: undefined,
+      activityStart: '',
+      activityEnd: '',
+      registrationPrice: 0,
+      buyCount: 0, //buyCount 小于1的时候 代表能无限购买
+      buyNum: 0,
+      vipCardId: 0,
+      activityList: [],
+      extConfig: {} as Record<string, any>,
+      vipType: 'VIP'
+    })
+
     const userInfo = computed(() => {
       const users = baseState.user.data
       return {
@@ -65,7 +79,9 @@ export default defineComponent({
         phone: users?.phone,
         avatar: users?.heardUrl,
         id: users?.userId,
-        userVip: users?.userVip
+        userVip: users?.userVip,
+        discountEndTime: users?.discountEndTime,
+        discountStartTime: users?.discountStartTime
       }
     })
 
@@ -193,6 +209,10 @@ export default defineComponent({
     }
 
     const calcSalePrice = (item: any) => {
+      // 有活动 以活动价格为准
+      if (item.id === activitData.vipCardId) {
+        return activitData.registrationPrice
+      }
       // discount
       if (item.discount === 1) {
         const tempPrice = Number(
@@ -281,25 +301,35 @@ export default defineComponent({
         orderStatus.orderObject.activityId = state.activityId
         orderStatus.orderObject.orderNo = ''
 
-        orderStatus.orderObject.orderList = [
-          {
-            orderType: state.tabActive,
-            goodsName: `小酷Ai ${state.tabActive} ${member.title}`,
-            id: member.id,
-            title: member.title,
-            num: 1, // 购买个数
-            salePrice: member.salePrice,
-            period: member.period,
-            vipEndDays: userInfo.value.userVip?.vipEndDays || 0, // 会员剩余天数
-            svipEndDays: userInfo.value.userVip?.svipEndDays || 0,
-            discount: member.discount, // 是否有折扣
-            discountPrice: member.discountPrice, // 折扣金额
-            price: calcSalePrice(member),
-            startTime: dayjs(startTime).format('YYYY-MM-DD'),
-            endTime: dayjs(endTime).format('YYYY-MM-DD'),
-            recomUserId: state.recomUserId
-          }
-        ]
+        const orderData = {
+          orderType: state.tabActive,
+          goodsName: `小酷Ai ${state.tabActive} ${member.title}`,
+          id: member.id,
+          title: member.title,
+          num: 1, // 购买个数
+          salePrice: member.salePrice,
+          period: member.period,
+          vipEndDays: userInfo.value.userVip?.vipEndDays || 0, // 会员剩余天数
+          svipEndDays: userInfo.value.userVip?.svipEndDays || 0,
+          discount: member.discount, // 是否有折扣
+          discountPrice: member.discountPrice, // 折扣金额
+          price: calcSalePrice(member),
+          startTime: dayjs(startTime).format('YYYY-MM-DD'),
+          endTime: dayjs(endTime).format('YYYY-MM-DD'),
+          recomUserId: state.recomUserId,
+          activityBuyCount: 0, // 活动购买限制次数
+          activityList: [], // 活动赠送的东西
+          discountEndTime: userInfo.value.discountEndTime, // 畅学卡结束时间
+          discountStartTime: userInfo.value.discountStartTime // 畅学卡开始时间
+        }
+        // 当能购买并且是当前活动商品时候加上 活动信息
+        const canBuyNum = activitData.buyCount - activitData.buyNum
+        if (member.id === activitData.vipCardId && canBuyNum > 0) {
+          orderData.activityBuyCount = canBuyNum
+          orderData.activityList = activitData.activityList
+          orderStatus.orderObject.activityId = activitData.activityId
+        }
+        orderStatus.orderObject.orderList = [orderData]
         router.push({
           path: '/orderDetail',
           query: {
@@ -382,6 +412,70 @@ export default defineComponent({
           }
         )
         const { list, ...more } = data
+
+        // 学生端 活动 有分享活动的时候 就不支持折扣活动
+        if (baseState.platformType === 'STUDENT' && !state.activityId) {
+          const activitRes = await request.post(
+            `/api-student/memberPriceSettings/getMemberBuyGift`
+          )
+          if (activitRes.code === 200 && activitRes.data) {
+            const {
+              activityStart,
+              activityEnd,
+              registrationPrice,
+              buyCount,
+              buyNum,
+              vipCardId,
+              activityRewardList,
+              extConfig,
+              id
+            } = activitRes.data || {}
+            // 匹配当前的 会员
+            const vipData = list.find(item => {
+              return item.id === vipCardId
+            })
+            // 当匹配上会员 并且没有达到购买限制的时候才有活动
+            if (vipData && (buyCount < 1 || buyCount > buyNum)) {
+              activitData.activityId = id
+              activitData.vipType = vipData.vipType
+              activitData.activityStart = activityStart
+              activitData.activityEnd = activityEnd
+              activitData.registrationPrice = registrationPrice
+              activitData.buyCount = buyCount < 1 ? 99999 : buyCount //buyCount 小于1的时候 代表能无限购买
+              activitData.buyNum = buyNum
+              activitData.vipCardId = vipCardId
+              extConfig && (activitData.extConfig = JSON.parse(extConfig))
+              activitData.activityList = (activityRewardList || []).map(
+                item => {
+                  const { rewardType, vipCardId, unit } = item.activityReward
+                  if (rewardType === 'DISCOUNT') {
+                    return {
+                      goodType: rewardType,
+                      goodName: `畅学卡 ${studyCardType[unit]}`,
+                      goodNum: 1,
+                      bizContent: vipCardId,
+                      giftFlag: true,
+                      vipEndDays: null,
+                      goodsNum: 1,
+                      unit
+                    }
+                  }
+                  return {
+                    goodType: rewardType,
+                    goodName: `小酷Ai ${rewardType} ${memberType[unit]}`,
+                    goodNum: 1,
+                    bizContent: vipCardId,
+                    giftFlag: true,
+                    vipEndDays: null,
+                    goodsNum: 1,
+                    unit
+                  }
+                }
+              )
+            }
+          }
+        }
+
         state.discountTeacher = {
           ...more
         }
@@ -516,6 +610,14 @@ export default defineComponent({
                       </span>
                     </div>
                     <div class={styles.vip_member_tip}></div>
+                    {/* 活动显示 */}
+                    {!!activitData.vipCardId &&
+                      activitData.vipType === 'VIP' &&
+                      activitData.extConfig?.title1 && (
+                        <div class={styles.activitTip}>
+                          {activitData.extConfig?.title1}
+                        </div>
+                      )}
                   </div>
                   <div
                     class={[
@@ -531,6 +633,14 @@ export default defineComponent({
                       </span>
                     </div>
                     <div class={styles.svip_member_tip}></div>
+                    {/* 活动显示 */}
+                    {!!activitData.vipCardId &&
+                      activitData.vipType === 'SVIP' &&
+                      activitData.extConfig?.title1 && (
+                        <div class={styles.activitTip}>
+                          {activitData.extConfig?.title1}
+                        </div>
+                      )}
                   </div>
                 </div>
               </>
@@ -621,9 +731,17 @@ export default defineComponent({
                               state.selectMember = member
                             }}
                           >
-                            {/* 只有永久才会有数量提示 */}
-                            {member.period === 'PERPETUAL' && (
-                              <span class={[styles.iconPermanent]}></span>
+                            {/* 有活动优先展示活动 */}
+                            {activitData.vipCardId === member.id &&
+                            activitData.extConfig?.title2 ? (
+                              <div class={styles.activitTip1}>
+                                {activitData.extConfig?.title2}
+                              </div>
+                            ) : (
+                              /* 只有永久才会有数量提示 */
+                              member.period === 'PERPETUAL' && (
+                                <span class={[styles.iconPermanent]}></span>
+                              )
                             )}
 
                             <p class={styles.s_title}>
@@ -660,9 +778,17 @@ export default defineComponent({
                         {/* 一条数据的样式 */}
                         {state.memberShowList.map((member: any) => (
                           <div class={[styles['system-item']]}>
-                            {/* 只有永久才会有数量提示 */}
-                            {member.period === 'PERPETUAL' && (
-                              <span class={[styles.iconPermanent]}></span>
+                            {/* 有活动优先展示活动 */}
+                            {activitData.vipCardId === member.id &&
+                            activitData.extConfig?.title2 ? (
+                              <div class={styles.activitTip1}>
+                                {activitData.extConfig?.title2}
+                              </div>
+                            ) : (
+                              /* 只有永久才会有数量提示 */
+                              member.period === 'PERPETUAL' && (
+                                <span class={[styles.iconPermanent]}></span>
+                              )
                             )}
 
                             {member.discount === 1 && (
@@ -724,7 +850,15 @@ export default defineComponent({
                   <span>原VIP会员天数升级为SVIP</span>
                 </div>
               )}
-
+            {/* 活动描述 */}
+            {state.selectMember?.id === activitData.vipCardId &&
+              activitData.extConfig?.describe && (
+                <div class={styles.activitTip2}>
+                  <div class={styles.activitTipText}>
+                    {activitData.extConfig?.describe}
+                  </div>
+                </div>
+              )}
             <MemberInteres type={state.tabActive} />
           </div>
         </div>

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

@@ -35,11 +35,26 @@
   }
 }
 
+.searchGroup {
+  background-color: #F8F9FC;
+  :global {
+    .van-search {
+      padding-top: 0;
+      // padding-bottom: 0;
+      padding-bottom: 12px;
+    }
+  }
+}
+
 .label {
   margin-right: 8px;
   font-size: 14px;
   color: #fff;
 
+  &.searchs {
+    color: #131415;
+  }
+
   :global {
 
     .van-list__loading,
@@ -59,6 +74,10 @@
   border-radius: 18px;
   background-color: #fff;
   margin: 14px;
+
+  &.alumnListOnly {
+    margin-top: 0;
+  }
 }
 
 .bgImg {

+ 46 - 3
src/views/music/list/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
+import { computed, defineComponent,  nextTick, onMounted, reactive, ref, watch } from 'vue'
 import { Sticky, List, Popup, Icon, Switch, Tabs, Tab } from 'vant'
 import Search from '@/components/col-search'
 import request from '@/helpers/request'
@@ -31,6 +31,14 @@ export default defineComponent({
       type: Boolean,
       default: false
     },
+    onlySearch: {
+      type: Boolean,
+      default: false
+    },
+    height: {
+      type: Number,
+      default: 0
+    },
     defauleParams: {
       type: Object,
       default: () => ({})
@@ -49,7 +57,7 @@ export default defineComponent({
     }
   },
   setup(
-    { hideSearch, defauleParams, onItemClick, teacherId, myself },
+    { hideSearch, onlySearch, height, defauleParams, onItemClick, teacherId, myself },
     { expose }
   ) {
     const { isLoading, state } = useAsyncState(
@@ -61,6 +69,13 @@ export default defineComponent({
       null
     )
 
+    const stickyHeight = ref(height || 0)
+
+    const updateStickyHeight = (height: number) => {
+      stickyHeight.value = height
+    }
+
+
     const teacherDetaultSubject = ref({
       id: '',
       name: ''
@@ -278,6 +293,7 @@ export default defineComponent({
     expose({
       onSearch,
       onComfirm,
+      updateStickyHeight,
       onComfirmSubject
     })
 
@@ -365,7 +381,34 @@ export default defineComponent({
               <img class={styles.bgImg} src={bgImg} />
             </>
           )}
-          <div class={styles.alumnList}>
+          {onlySearch ? <Sticky position='top' offsetTop={stickyHeight.value as any}><Search
+                  onSearch={onSearch}
+                  background="transparent"
+                  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>
+                    )
+                  }}
+                /></Sticky> : ''}
+
+          <div class={[styles.alumnList, onlySearch && styles.alumnListOnly]}>
             <List
               loading={loading.value}
               finished={finished.value}

+ 104 - 0
src/views/order-detail/add-discount/index.module.less

@@ -0,0 +1,104 @@
+
+.addDiscount {
+  .selectDiscount {
+    padding-left: 11px;
+  }
+  .boxStyle {
+    background: transparent !important;
+    width: 18px;
+    height: 18px;
+    border: transparent !important;
+    
+  }
+  :global {
+    .van-checkbox {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+      margin-right: 4px;
+    }
+    .van-checkbox__icon {
+      height: 18px;
+      line-height: 18px;
+      display: inline-block;
+      vertical-align: middle;
+    }
+    .van-checkbox__label {
+      line-height: 18px;
+    }
+  }
+}
+
+.container {
+  display: flex;
+  .memberLogo {
+    flex-shrink: 0;
+    margin-right: 10px;
+    width: 76px;
+    height: 76px;
+  }
+
+  .info{
+    flex: 1 auto;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+
+    .top {
+      display: flex;
+      justify-content: space-between;
+      font-size: 14px;
+        color: #131415;
+        line-height: 22px;
+      .title {
+        font-weight: 600;
+      }
+    }
+
+    .center {
+      .t {
+        display: inline-block;
+        background: #FEE8E4;
+        border-radius: 4px;
+        font-size: 12px;
+        color: #333333;
+        line-height: 1;
+        padding: 2px 8px 2px 0;
+        .dis {
+          background: linear-gradient( 315deg, #FF3C83 0%, #FF0704 51%, #FF7A4F 100%);
+          border-radius: 4px;
+          color: #FFFFFF;
+          font-weight: bold;
+          padding: 0 4px;
+          margin-right: 8px;
+        }
+        .text {
+          vertical-align: middle;
+        }
+      }
+    }
+
+    .bottom {
+      font-size: 12px;
+      color: #999999;
+      line-height: 18px;
+    }
+  }
+}
+
+.cellGroup {
+  border-radius: 10px;
+  overflow: hidden;
+}
+.timer {
+  font-size: 14px;
+  font-weight: 500;
+  color: #FF1A00;
+  line-height: 20px;
+}
+
+.timerCell {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}

部分文件因文件數量過多而無法顯示