lex-xin 3 일 전
부모
커밋
6f9ea3e024
51개의 변경된 파일3715개의 추가작업 그리고 39개의 파일을 삭제
  1. 4 1
      src/business-components/user-detail/index.tsx
  2. 3 0
      src/constant/index.ts
  3. 8 0
      src/router/routes-student.ts
  4. 16 0
      src/router/routes-teacher.ts
  5. BIN
      src/student/discount-card/images/card-bg.png
  6. BIN
      src/student/down-load/images/student_bg.png
  7. 29 0
      src/student/group-class/index.module.less
  8. 193 0
      src/student/group-class/index.tsx
  9. 67 0
      src/student/group-class/live-detail.module.less
  10. 395 0
      src/student/group-class/live-detail.tsx
  11. 99 0
      src/student/group-class/live-item.module.less
  12. 109 0
      src/student/group-class/live-item.tsx
  13. 167 0
      src/student/teacher-dependent/components/group.tsx
  14. 6 0
      src/student/teacher-dependent/teacher-home.tsx
  15. 1 1
      src/student/trade/tradeOrder.ts
  16. 2 0
      src/teacher/extend-plan/index.tsx
  17. 70 0
      src/teacher/group-class/create-components/arrange.module.less
  18. 383 0
      src/teacher/group-class/create-components/arrange.tsx
  19. 7 0
      src/teacher/group-class/create-components/course-plan.module.less
  20. 91 0
      src/teacher/group-class/create-components/course-plan.tsx
  21. 74 0
      src/teacher/group-class/create-components/course-start.module.less
  22. 335 0
      src/teacher/group-class/create-components/course-start.tsx
  23. 17 0
      src/teacher/group-class/create-components/course.module.less
  24. 263 0
      src/teacher/group-class/create-components/course.tsx
  25. 48 0
      src/teacher/group-class/create-components/createState.ts
  26. 2 0
      src/teacher/group-class/create-components/detail.module.less
  27. 186 0
      src/teacher/group-class/create-components/detail.tsx
  28. 32 0
      src/teacher/group-class/create-components/steps.module.less
  29. 112 0
      src/teacher/group-class/create-components/steps.tsx
  30. 21 0
      src/teacher/group-class/create.module.less
  31. 143 0
      src/teacher/group-class/create.tsx
  32. 22 0
      src/teacher/group-class/group-detail.module.less
  33. 331 0
      src/teacher/group-class/group-detail.tsx
  34. BIN
      src/teacher/group-class/images/icon_arrange_active.png
  35. BIN
      src/teacher/group-class/images/icon_arrange_default.png
  36. BIN
      src/teacher/group-class/images/icon_course_active.png
  37. BIN
      src/teacher/group-class/images/icon_plan_active.png
  38. BIN
      src/teacher/group-class/images/icon_plan_default.png
  39. BIN
      src/teacher/group-class/images/icon_start_active.png
  40. BIN
      src/teacher/group-class/images/icon_start_default.png
  41. 38 16
      src/teacher/income-consus/echarts.ts
  42. 9 3
      src/teacher/income-consus/index.module.less
  43. 38 14
      src/teacher/income-consus/index.tsx
  44. 27 0
      src/teacher/share-page/share-group/index.module.less
  45. 204 0
      src/teacher/share-page/share-group/index.tsx
  46. 1 1
      src/tenant/trade/tradeOrder.ts
  47. 6 1
      src/views/order-detail/index.tsx
  48. 74 0
      src/views/order-detail/order-group/index.module.less
  49. 77 0
      src/views/order-detail/order-group/index.tsx
  50. 3 2
      src/views/order-detail/orderStatus.ts
  51. 2 0
      src/views/order-detail/use-coupons/index.tsx

+ 4 - 1
src/business-components/user-detail/index.tsx

@@ -34,6 +34,7 @@ interface UserType {
   lessonPrice: number
   lessonNum?: number
   lessonDesc?: string
+  mixStudentNum?: string
   lessonCoverUrl: string
   lessonName: string
   auditVersion: number
@@ -164,7 +165,9 @@ export default defineComponent({
                             ? '领取'
                             : '购买'}
                         </>
-                      ) : (
+                      ) : this.userInfo.type === "group" ?
+                        ((this.userInfo.mixStudentNum || 0 > 0) ? <>剩余{this.userInfo.mixStudentNum}个名额</> : '')
+                       : (
                         <>{this.userInfo.buyNum}人学习</>
                       )}
                     </div>

+ 3 - 0
src/constant/index.ts

@@ -1,5 +1,6 @@
 export const goodsType = {
   LIVE: '直播课',
+  GROUP: '小组课',
   PRACTICE: '趣纠课',
   VIDEO: '视频课',
   VIP: '开通VIP会员',
@@ -63,10 +64,12 @@ export const courseType = {
 export const bizStatus = {
   PRACTICE: '趣纠课',
   LIVE: '直播课',
+  GROUP: '小组课',
   VIDEO: '视频课',
   MUSIC: '乐谱',
   WITHDRAWAL: '提现',
   LIVE_SHARE: '直播课分润',
+  GROUP_SHARE: '小组课分润',
   VIDEO_SHARE: '视频课分润',
   MUSIC_SHARE: '乐谱分润',
   VIP_SHARE: '会员分润',

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

@@ -73,6 +73,14 @@ export default [
         }
       },
       {
+        path: '/groupDetail',
+        name: 'groupDetail',
+        component: () => import('@/student/group-class/live-detail'),
+        meta: {
+          title: '小组课详情'
+        }
+      },
+      {
         path: '/memberActive',
         name: 'memberActive',
         component: () => import('@/student/member-center/member-active'),

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

@@ -194,6 +194,22 @@ export default [
         }
       },
       {
+        path: '/groupCreate',
+        name: 'groupCreate',
+        component: () => import('@/teacher/group-class/create'),
+        meta: {
+          title: '创建小组课'
+        }
+      },
+      {
+        path: '/groupDetail',
+        name: 'groupDetail',
+        component: () => import('@/teacher/group-class/group-detail'),
+        meta: {
+          title: '小组课详情'
+        }
+      },
+      {
         path: '/practiceSetting',
         name: 'practiceSetting',
         component: () => import('@/teacher/practice-class/practice-setting'),

BIN
src/student/discount-card/images/card-bg.png


BIN
src/student/down-load/images/student_bg.png


+ 29 - 0
src/student/group-class/index.module.less

@@ -0,0 +1,29 @@
+.classHeader {
+  // background: linear-gradient(180deg, #59e5d5 0%, #fff 100%);
+  background: linear-gradient(#59e5d5, 30%, #ffffff);
+}
+.banner {
+  background: #fff;
+  padding: 12px 14px;
+}
+
+.label {
+  margin-right: 8px;
+  font-size: 14px;
+  :global {
+    .van-list__loading,
+    .van-list__finished-text,
+    .van-list__error-text {
+      width: 100%;
+    }
+    .iconfont-down {
+      margin-left: 4px;
+    }
+  }
+}
+
+.liveClass {
+  :global(.van-sticky--fixed) {
+    box-shadow: 10px 10px 10px var(--box-shadow-color);
+  }
+}

+ 193 - 0
src/student/group-class/index.tsx

@@ -0,0 +1,193 @@
+import ColHeader from '@/components/col-header'
+import ColSearch from '@/components/col-search'
+import { Sticky, Image, List, Icon, Popup } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import request from '@/helpers/request'
+import ColResult from '@/components/col-result'
+import LiveItem from './live-item'
+
+import banner from '../video-class/images/banner.png'
+import { state } from '@/state'
+import OrganSearch from '../practice-class/model/organ-search'
+import { useEventTracking } from '@/helpers/hooks'
+
+export default defineComponent({
+  name: 'liveClass',
+  data() {
+    const sessionSubjectId = sessionStorage.getItem('liveClassSubjectId')
+
+    return {
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      searchStatus: false,
+      openStatus: false,
+      subjectList: [],
+      sessionSubjectId,
+      params: {
+        search: '',
+        courseType: "GROUP",
+        subjectId: (sessionSubjectId ||
+          state.user.data?.subjectId ||
+          null) as any,
+        subjectName: '',
+        groupStatus: 'APPLY',
+        page: 1,
+        rows: 20
+      }
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.get('/api-student/subject/subjectSelect')
+      this.subjectList = res.data || []
+    } catch {}
+    let subjectName = ''
+    this.subjectList.forEach((item: any) => {
+      item.subjects?.forEach((child: any) => {
+        if (child.id === Number(this.sessionSubjectId)) {
+          subjectName = child.name
+        }
+      })
+    })
+    this.params.subjectName = subjectName || state.user.data?.subjectName || ''
+
+    sessionStorage.removeItem('liveClassSubjectId')
+    useEventTracking('直播课')
+  },
+  methods: {
+    onSort() {
+      this.params.page = 1
+      this.list = []
+      this.dataShow = true // 判断是否有数据
+      this.loading = false
+      this.finished = false
+      this.searchStatus = false
+      this.getList()
+    },
+    onSearch(value: string) {
+      this.params.search = value
+      this.onSort()
+    },
+    async getList() {
+      try {
+        const params: any = {
+          ...this.params
+        }
+        if (state.version) {
+          params.version = state.version || '' // 处理ios审核版本
+          params.platform = 'ios-student'
+        }
+        const res = await request.post(
+          '/api-student/courseGroup/queryPageCourseGroup',
+          {
+            data: {
+              ...params
+            }
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    },
+    onDetail(item: any) {
+      this.params.subjectId &&
+        sessionStorage.setItem('liveClassSubjectId', this.params.subjectId)
+      this.$router.push({
+        path: '/groupDetail',
+        query: {
+          groupId: item.courseGroupId
+        }
+      })
+    }
+  },
+  render() {
+    return (
+      <div class={styles.liveClass}>
+        <Sticky offsetTop={0} position="top">
+          <ColHeader
+            class={styles.classHeader}
+            border={false}
+            isFixed={false}
+            background="transparent"
+          />
+          <ColSearch
+            placeholder="请输入老师名称/课程名称"
+            onSearch={this.onSearch}
+            v-slots={{
+              left: () => (
+                <div
+                  class={styles.label}
+                  onClick={() => {
+                    this.searchStatus = !this.searchStatus
+                    this.openStatus = !this.openStatus
+                  }}
+                >
+                  {this.params.subjectName}
+                  <Icon
+                    classPrefix="iconfont"
+                    name="down"
+                    size={12}
+                    color="#333"
+                  />
+                </div>
+              )
+            }}
+          />
+        </Sticky>
+
+        <div class={styles.banner}>
+          <Image src={banner} />
+        </div>
+
+        {this.dataShow ? (
+          <List
+            v-model:loading={this.loading}
+            finished={this.finished}
+            finishedText=" "
+            class={[styles.liveList]}
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <LiveItem onClick={this.onDetail} liveInfo={item} />
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无小组课" />
+        )}
+
+        <Popup
+          show={this.searchStatus}
+          position="bottom"
+          round
+          closeable
+          safe-area-inset-bottom
+          onClose={() => (this.searchStatus = false)}
+          onClosed={() => (this.openStatus = false)}
+        >
+          {this.openStatus && (
+            <OrganSearch
+              subjectList={this.subjectList}
+              onSort={this.onSort}
+              v-model={this.params.subjectId}
+              v-model:subjectName={this.params.subjectName}
+            />
+          )}
+        </Popup>
+      </div>
+    )
+  }
+})

+ 67 - 0
src/student/group-class/live-detail.module.less

@@ -0,0 +1,67 @@
+.live-detail {
+  .introduction {
+    color: #7a7a7a;
+    line-height: 23px;
+    padding-bottom: 8px;
+  }
+
+  :global {
+    .van-tabs__wrap {
+      margin-bottom: 15px;
+    }
+
+    .van-button--disabled {
+      opacity: 0.5;
+    }
+  }
+
+  .tips {
+    background-color: white;
+    border-radius: 10px;
+    margin: 14px 12px;
+    padding: var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
+
+    h3 {
+      display: flex;
+      font-size: 14px;
+      font-weight: 500;
+      color: #333333;
+      line-height: 1;
+    }
+
+    p {
+      text-align: justify;
+      padding-top: 9px;
+      font-size: 12px;
+      color: #7a7a7a;
+      line-height: 23px;
+    }
+
+    :global {
+      .van-icon {
+        margin-right: 6px;
+      }
+    }
+  }
+
+  .btnMore {
+    display: flex;
+    justify-content: space-around;
+
+    :global {
+      .van-button {
+        width: 100%;
+      }
+    }
+  }
+
+  .shareCourse {
+    margin: 0;
+    padding: 8px;
+    background: #fff;
+
+    :global(.itemTitle) {
+      max-width: 110px !important;
+    }
+  }
+}

+ 395 - 0
src/student/group-class/live-detail.tsx

@@ -0,0 +1,395 @@
+import CoursePlanStep from '@/business-components/course-plan-step'
+import SectionDetail from '@/business-components/section-detail'
+import UserDetail from '@/business-components/user-detail'
+import request from '@/helpers/request'
+import dayjs from 'dayjs'
+import { Icon, Sticky, Button, Dialog, Toast, Popup } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './live-detail.module.less'
+import iconTips from '@common/images/icon_tips.png'
+import { onSubmitZero, orderStatus } from '@/views/order-detail/orderStatus'
+import ColHeader from '@/components/col-header'
+import { postMessage } from '@/helpers/native-message'
+import ColSticky from '@/components/col-sticky'
+import ColShare from '@/components/col-share'
+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 { tradeOrder } from '../trade/tradeOrder'
+import { courseType } from '@/constant';
+interface IProps {
+  courseTime: string
+  coursePlan: string
+  videoPosterUrl?: string
+  roomUid?: string
+  liveState?: number
+  id?: number | string
+}
+export default defineComponent({
+  name: 'LiveDetail',
+  data() {
+    const query = this.$route.query
+    return {
+      joinRoom: query.joinRoom, // 原生传递过来的参数,判断是否进入直播间
+      recomUserId: query.recomUserId, // 推荐人id
+      groupId: query.groupId,
+      courseId: query.classId,
+      platform: query.p, // 属于哪个平台,//机构老师 tenant,平台老师 无
+      live: {} as any,
+      shareStatus: false,
+      shareUrl: ''
+    }
+  },
+  computed: {
+    userInfo() {
+      const live = this.live as any
+      // console.log('live', live)
+      const planList = live.planList || []
+      const startTime = planList[0]?.startTime || new Date()
+      const endTime = planList[0]?.endTime || new Date()
+      const studentNum = live.maxStudentNum || 0 - live.studentCount || 0
+      return {
+        avatar: live.avatar,
+        headUrl: live.avatar,
+        username: live.userName || `游客${live.teacherId || ''}`,
+        id: live.teacherId,
+        startTime:
+          `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(startTime).format(
+            'HH:mm'
+          )}~${dayjs(endTime).format('HH:mm')}` || '',
+        buyNum: live.studentCount,
+        type: 'group',
+        lessonId: live.courseGroupId,
+        lessonPrice: live.coursePrice,
+        lessonNum: live.courseNum,
+        mixStudentNum: studentNum > 0 ? studentNum : 0,
+        lessonDesc: live.courseIntroduce,
+        lessonCoverUrl: live.backgroundPic || live.backgroundPicTemplate,
+        lessonName: live.courseGroupName,
+        subjectName: live.subjectName,
+        courseStartTime: live.courseStartTime,
+        auditVersion: live.auditVersion || 0,
+        isDegree: live.degreeFlag ? true : false,
+        isTeacher: live.teacherFlag ? true : false
+      }
+    },
+    platformStatus() {
+      const userInfo = state.user.data as any
+      // 是机构学生 并且 是机构老师分享
+      const query = this.$route.query
+      return userInfo.tenantId > 0 && query.p == 'tenant'
+    },
+    courseInfo() {
+      const tempArr = [] as IProps[]
+      const coursePlanList = this.live.planList || []
+      coursePlanList.forEach((item: any) => {
+        const startTime = item.startTime || new Date()
+        const endTime = item.endTime || new Date()
+        tempArr.push({
+          courseTime: `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(
+            startTime
+          ).format('HH:mm')}~${dayjs(endTime).format('HH:mm')}`,
+          coursePlan: item.plan,
+          roomUid: item.roomUid,
+          liveState: item.liveState,
+          id: item.courseId
+        })
+      })
+      return tempArr || []
+    },
+    salesEndDate() {
+      const live = this.live as any
+      return dayjs(live.salesEndDate || new Date()).format('YYYY-MM-DD')
+    },
+    liveStatus() {
+      const coursePlanList = this.live.planList || []
+      const tempObj = {
+        status: false,
+        liveStatus: 0,
+        roomUid: ''
+      }
+      coursePlanList.forEach((item: any) => {
+        if (item.courseId === Number(this.courseId)) {
+          tempObj.status = true
+          tempObj.liveStatus = item.liveStatus
+          tempObj.roomUid = item.roomUid
+        }
+      })
+      return tempObj
+    }
+  },
+  async mounted() {
+    await this._init()
+    if (/(localhost|192)/g.test(location.origin)) {
+      this.shareUrl = `https://dev.colexiu.com/teacher/#/shareLive?recomUserId=${state.user.data?.userId}&groupId=${this.groupId}&userType=${state.platformType}&p=tenant`
+    } else {
+      this.shareUrl = `${location.origin}/teacher/#/shareLive?recomUserId=${state.user.data?.userId}&groupId=${this.groupId}&userType=${state.platformType}&p=tenant`
+    }
+  },
+  methods: {
+    async _init() {
+      try {
+        const res = await request.get(
+          '/api-student/courseGroup/queryLiveCourseInfo',
+          {
+            params: {
+              groupId: this.groupId
+            }
+          }
+        )
+        this.live = res.data || {}
+        console.log(this.live, "list");
+      } catch {}
+    },
+    async onJoinRoom() {
+      try {
+        const res = await request.get(
+          '/api-student/courseGroup/queryLiveCourseInfo',
+          {
+            params: {
+              courseType: 'GROUP',
+              groupId: this.groupId
+            }
+          }
+        )
+        const result = res.data || {}
+        const coursePlanList = result.planList || []
+        let tempObj: any = {}
+        coursePlanList.forEach((item: any) => {
+          if (item.courseId === Number(this.courseId)) {
+            tempObj = item
+          }
+        })
+        if (tempObj && tempObj.liveState === 1) {
+          postMessage({
+            api: 'joinLiveRoom',
+            content: {
+              roomId: tempObj.roomUid,
+              teacherId: this.live.teacherId
+            }
+          })
+        } else if (tempObj && tempObj.liveState === 2) {
+          setTimeout(() => {
+            Toast('课程已结束')
+          }, 100)
+        } else {
+          setTimeout(() => {
+            Toast('课程尚未开始,请耐心等候')
+          }, 100)
+        }
+      } catch {}
+    },
+    initLive () {
+      const live = this.live
+      orderStatus.orderObject.orderType = 'GROUP'
+      orderStatus.orderObject.orderName = '小组课购买'
+      orderStatus.orderObject.orderDesc = '小组课购买'
+      orderStatus.orderObject.actualPrice = live.coursePrice
+      orderStatus.orderObject.recomUserId = this.recomUserId
+      orderStatus.orderObject.orderNo = ''
+      orderStatus.orderObject.orderList = [
+        {
+          orderType: 'GROUP',
+          goodsName: '小组课购买',
+          courseGroupId: live.courseGroupId,
+          courseGroupName: live.courseGroupName,
+          coursePrice: live.coursePrice,
+          price: live.coursePrice,
+          teacherName: live.userName || `游客${live.teacherId || ''}`,
+          teacherId: live.teacherId,
+          avatar: live.avatar,
+          courseInfo: this.courseInfo,
+          recomUserId: this.recomUserId
+        }
+      ]
+    },
+    async onBuy() {
+      try {
+        const live = this.live
+        // 判断是否是0无订单
+        if (live.coursePrice <= 0) {
+          this.initLive()
+          await onSubmitZero(() => {
+            Dialog.alert({
+              message: '领取成功',
+              confirmButtonText: '确定',
+              confirmButtonColor: '#2dc7aa'
+            }).then(() => {
+              this._init()
+            })
+          })
+          return
+        }
+        const res = await request.post(
+          '/api-student/userOrder/getPendingOrder',
+          {
+            data: {
+              goodType: 'GROUP',
+              bizId: this.groupId
+            }
+          }
+        )
+
+        const result = res.data
+        if (result) {
+          Dialog.confirm({
+            title: '提示',
+            message: '您有一个未支付的订单,是否继续支付?',
+            confirmButtonColor: '#269a93',
+            cancelButtonText: '取消订单',
+            confirmButtonText: '继续支付'
+          })
+            .then(async () => {
+              tradeOrder(result, this.routerTo)
+            })
+            .catch(() => {
+              Dialog.close()
+              // 只用取消订单,不用做其它处理
+              this.cancelPayment(result.orderNo)
+            })
+        } else {
+          this.initLive()
+          this.routerTo()
+        }
+      } catch {
+        //
+      }
+    },
+    routerTo() {
+      const live = this.live
+      this.$router.push({
+        path: '/orderDetail',
+        query: {
+          orderType: 'GROUP',
+          courseGroupId: live.courseGroupId
+        }
+      })
+    },
+    async cancelPayment(orderNo: string) {
+      try {
+        await request.post('/api-student/userOrder/orderCancel', {
+          data: {
+            orderNo
+          }
+        })
+        // this.routerTo()
+      } catch {}
+    }
+  },
+  render() {
+    return (
+      <div class={[styles['live-detail'], 'mb12']}>
+        <ColHeader
+          v-slots={{
+            right: () => (
+              <img src={iconShare} onClick={() => (this.shareStatus = true)} />
+            )
+          }}
+        />
+        <UserDetail
+          userInfo={this.userInfo}
+          showBuy={true}
+          onUserDetail={(item: any) => {
+            if (state.platformType === 'STUDENT' && browser().isApp) {
+              this.$router.push({
+                path: '/teacherHome',
+                query: {
+                  teacherId: item.id,
+                  tabs: 'live'
+                }
+              })
+            }
+          }}
+        />
+        <SectionDetail border>
+          <p class={styles.introduction}>{this.userInfo.lessonDesc}</p>
+        </SectionDetail>
+
+        <SectionDetail
+          title="课程列表"
+          icon="courseList"
+          border
+          contentStyle={{ paddingTop: '0' }}
+        >
+          {this.courseInfo.length > 0 && (
+            <CoursePlanStep
+              courseInfo={this.courseInfo}
+              courseId={Number(this.courseId) || 0}
+            />
+          )}
+        </SectionDetail>
+
+        {/* <div class={styles.tips}>
+          <h3>
+            <Icon name={iconTips} size={15} />
+            温馨提示
+          </h3>
+          <p>
+            1、该小组课程销售截止后,报名人数若少于
+            {this.live.mixStudentNum || 0}
+            人将取消开课,已购买学员付费金额将自动返还,请您放心购买;
+            <br />
+            2、小组课教学计划中的上课时间为老师预计时间,实际上课时间以老师开启直播时间为准;
+            <br />
+            3、若您错过老师直播,可通过视频回放观看完整课程。
+          </p>
+        </div> */}
+        {this.courseInfo.length > 0 && this.live.existBuy !== 1 && (
+          <ColSticky position="bottom" background="white">
+            <div class={['btnGroup', styles.btnMore]}>
+              <Button
+                block
+                round
+                type="primary"
+                onClick={this.onBuy}
+                disabled={this.platformStatus}
+              >
+                {this.live.coursePrice <= 0 ? '免费领取' : `立即购买`}
+              </Button>
+            </div>
+          </ColSticky>
+        )}
+
+        {this.joinRoom == '1' && this.liveStatus.liveStatus !== 2 && (
+          <ColSticky position="bottom" background="white">
+            <div class={['btnGroup']} style={{ paddingTop: '12px' }}>
+              <Button block round type="primary" onClick={this.onJoinRoom}>
+                进入直播间
+              </Button>
+            </div>
+          </ColSticky>
+        )}
+
+        <Popup
+          v-model:show={this.shareStatus}
+          style={{ background: 'transparent' }}
+        >
+          <ColShare
+            teacherId={this.userInfo.id}
+            shareUrl={this.shareUrl}
+            shareType="live"
+          >
+            <LiveItem
+              class={styles.shareCourse}
+              liveInfo={{
+                backgroundPic: this.userInfo.lessonCoverUrl,
+                courseGroupId: this.userInfo.lessonId,
+                courseGroupName: this.userInfo.lessonName,
+                courseNum: this.userInfo.lessonNum,
+                coursePrice: this.userInfo.lessonPrice,
+                teacherName: this.userInfo.username,
+                teacherId: this.userInfo.id,
+                avatar: this.userInfo.avatar,
+                studentCount: this.userInfo.buyNum,
+                courseStartTime: this.userInfo.courseStartTime,
+                existBuy: 0,
+                subjectName: this.userInfo.subjectName
+              }}
+            />
+          </ColShare>
+        </Popup>
+      </div>
+    )
+  }
+})

+ 99 - 0
src/student/group-class/live-item.module.less

@@ -0,0 +1,99 @@
+.liveItem {
+  margin: 10px 10px 0;
+  width: auto !important;
+  border-radius: 8px;
+  padding: 10px;
+
+  .liCover {
+    width: 170px;
+    height: 96px;
+    background: #c6c7cb;
+    border-radius: 8px;
+    overflow: hidden;
+    margin-right: 8px;
+    vertical-align: middle;
+  }
+
+  .liTitle {
+    padding-top: 4px;
+    font-size: 14px;
+    font-weight: 600;
+    color: #333333;
+    line-height: 20px;
+    max-width: 150px;
+  }
+
+  .liUserInfo {
+    padding-top: 4px;
+    padding-bottom: 9px;
+    font-size: 12px;
+    color: #6a6a6a;
+    line-height: 16px;
+  }
+
+  .liteachername {
+    display: flex;
+    align-items: center;
+    margin-bottom: 6px;
+  }
+
+  .liteacherIcon {
+    width: 18px;
+    height: 18px;
+    border-radius: 50%;
+    overflow: hidden;
+    margin-right: 4px;
+  }
+
+  .liPrice {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    .price {
+      color: #fa6400;
+      font-size: 14px;
+      margin-right: 10px;
+      font-weight: 600;
+      i {
+        font-style: normal;
+        font-size: 11px;
+      }
+    }
+
+    .classNum {
+      background: #fff1de;
+      border-radius: 4px;
+      font-size: 12px;
+      color: #ff9300;
+      line-height: 18px;
+      padding: 1px 4px;
+    }
+
+    .num,
+    .buyNum {
+      font-size: 12px;
+      color: #ff802c;
+      line-height: 18px;
+    }
+
+    .buyNum {
+      color: #999999;
+      display: flex;
+      align-items: center;
+      line-height: 1;
+    }
+  }
+
+  .subjectName {
+    position: absolute;
+    top: 8px;
+    left: 8px;
+    font-size: 12px;
+    padding: 3px 5px;
+    color: #ffffff;
+    line-height: 1;
+    border-radius: 1px;
+    background: rgba(0, 0, 0, 0.29);
+  }
+}

+ 109 - 0
src/student/group-class/live-item.tsx

@@ -0,0 +1,109 @@
+import { defineComponent, PropType } from 'vue'
+import { Image, CellGroup, Cell, Icon } from 'vant'
+import styles from './live-item.module.less'
+import dayjs from 'dayjs'
+
+import iconSuccess from '@common/images/icon_success.png'
+import iconTeacher from '@common/images/icon_teacher.png'
+
+interface IProps {
+  backgroundPic: string
+  courseGroupId: number
+  courseGroupName: string
+  courseNum: string
+  courseStartTime: number
+  coursePrice: number
+  teacherName: string
+  teacherId: number
+  avatar?: string
+  studentCount: number
+  existBuy: number
+  subjectName: string
+}
+
+export default defineComponent({
+  name: 'liveItem',
+  props: {
+    onClick: {
+      type: Function,
+      default: (item?: any) => {}
+    },
+    liveInfo: {
+      type: Object as PropType<IProps>,
+      default: {}
+    }
+  },
+  render() {
+    return (
+      <Cell
+        center
+        border={false}
+        class={styles.liveItem}
+        onClick={() => this.onClick(this.liveInfo)}
+        v-slots={{
+          icon: () => (
+            <div style={{ position: 'relative' }}>
+              <Image
+                class={styles.liCover}
+                fit="cover"
+                src={this.liveInfo.backgroundPic}
+              />
+              <span class={styles.subjectName}>
+                {this.liveInfo.subjectName}
+              </span>
+            </div>
+          ),
+          title: () => (
+            <div>
+              <div class={[styles.liTitle, 'van-ellipsis']}>
+                {this.liveInfo.courseGroupName}
+              </div>
+              <div class={styles.liUserInfo}>
+                <p class={styles.liteachername}>
+                  {/* 老师: */}
+                  <Image
+                    src={this.liveInfo.avatar || iconTeacher}
+                    class={styles.liteacherIcon}
+                  />
+                  {this.liveInfo.teacherName ||
+                    `游客${this.liveInfo.teacherId}`}
+                </p>
+                <p>
+                  开课时间:
+                  {dayjs(this.liveInfo.courseStartTime).format(
+                    'MM月DD日 HH:mm'
+                  )}
+                </p>
+              </div>
+              <div class={styles.liPrice}>
+                <p>
+                  {this.liveInfo.coursePrice > 0 && (
+                    <span class={styles.price}>
+                      <i>¥</i>
+                      {this.liveInfo.coursePrice}
+                    </span>
+                  )}
+
+                  <span class={styles.classNum}>
+                    {this.liveInfo.courseNum}课时
+                  </span>
+                </p>
+                {this.liveInfo.existBuy === 1 && (
+                  <span class={styles.buyNum}>
+                    {/* <Icon name={iconSuccess} size="15" /> */}
+                    学习
+                  </span>
+                )}
+                {/* : (
+                <span class={styles.num}>
+                   {this.liveInfo.studentCount}人学习
+                </span>
+                ) */}
+              </div>
+            </div>
+          )
+        }}
+      />
+    )
+  }
+})

+ 167 - 0
src/student/teacher-dependent/components/group.tsx

@@ -0,0 +1,167 @@
+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 iconLive from '../images/icon-live.png'
+import request from '@/helpers/request'
+import dayjs from 'dayjs'
+import { state } from '@/state'
+import Tips from './tips'
+
+export default defineComponent({
+  name: 'live',
+  data() {
+    const query = this.$route.query
+    return {
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      params: {
+        teacherId: query.teacherId,
+        courseType: "GROUP",
+        groupStatus: 'APPLY',
+        page: 1,
+        rows: 20
+      }
+    }
+  },
+  mounted() {
+    this.getList()
+  },
+  methods: {
+    formatTime(time: string) {
+      const timeStr = dayjs(time || new Date())
+      const weekStr = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
+      // console.log(timeStr.day())
+
+      return timeStr.format('YYYY-MM-DD')  + `(${weekStr[timeStr.day()]})`
+    },
+    async getList() {
+      try {
+        const params: any = {
+          ...this.params
+        }
+        if (state.version) {
+          params.version = state.version || '' // 处理ios审核版本
+          params.platform = 'ios-student'
+        }
+        const res = await request.post(
+          '/api-student/courseGroup/queryPageCourseGroup',
+          {
+            data: {
+              ...params
+            }
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    },
+    onDetail(item: any) {
+      this.$router.push({
+        path: '/groupDetail',
+        query: {
+          groupId: item.courseGroupId
+        }
+      })
+    }
+  },
+  render() {
+    return (
+      <>
+        <Tips
+          type="GROUP"
+          class={styles.tips}
+          title="什么是小组课?"
+          content="小组课是老师根据教学目的编排的课程,固定时间进行1V5线上授课。您可根据老师开放的课程内容和您的时间安排,选择您感兴趣的课程组进行学习。"
+        />
+        {this.dataShow ? (
+          <List
+            class={styles.liveList}
+            v-model:loading={this.loading}
+            immediateCheck={false}
+            finished={this.finished}
+            finishedText="没有更多了"
+          >
+            {this.list.map((item: any) => (
+              <CellGroup
+                class={styles.liveGroup}
+                border={false}
+                onClick={() => this.onDetail(item)}
+              >
+                <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.lean}>
+                      <span class={styles.num}>
+                        {item.studentCount}人学习
+                      </span>
+                    </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}课时
+                      </span>
+                    </div>
+                  </div>
+                </div>
+              </CellGroup>
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无小组课" />
+        )}
+      </>
+    )
+
+    {
+      /* <List
+v-model:loading={this.loading}
+finished={this.finished}
+finishedText="没有更多了"
+onLoad={this.getList}
+>
+{this.buyUserList.map(item => (
+  <UserList class="mb12" users={item} />
+))}
+</List> */
+    }
+  }
+})

+ 6 - 0
src/student/teacher-dependent/teacher-home.tsx

@@ -18,6 +18,7 @@ import JoinChat from './model/join-chat'
 import FansList from './model/fans-list'
 import MusicList from '@/views/music/list'
 import { setLogin } from '@/state'
+import Group from './components/group'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `./images/${fileName}`
@@ -216,6 +217,11 @@ export default defineComponent({
               )}
             </div>
           </Tab>
+          <Tab title="小组课" name="group">
+            <div style={{ minHeight: this.homeContaiterHeight }}>
+              {this.tabs === 'group' && <Group />}
+            </div>
+          </Tab>
           <Tab title="直播课" name="live">
             <div style={{ minHeight: this.homeContaiterHeight }}>
               {this.tabs === 'live' && <Live />}

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

@@ -41,7 +41,7 @@ export const formatOrderDetail = async (item: any, amount?: IAmount) => {
   const type = item.goodType
   let tempList: any = {}
   switch (type) {
-    case 'LIVE':
+    case 'LIVE': case "GROUP":
       {
         try {
           const live = await getLiveDetail(item.bizId)

+ 2 - 0
src/teacher/extend-plan/index.tsx

@@ -60,6 +60,7 @@ export default defineComponent({
       actions: [
         { name: '全部推广', value: '' },
         { name: '直播课', value: 'LIVE_SHARE' },
+        { name: "小组课", value: "GROUP_SHARE" },
         { name: '视频课', value: 'VIDEO_SHARE' },
         { name: '小酷Ai会员', value: 'VIP_SHARE' },
         { name: '曲谱', value: 'MUSIC_SHARE' },
@@ -69,6 +70,7 @@ export default defineComponent({
       ],
       actionsType: {
         LIVE_SHARE: '直播课',
+        GROUP_SHARE: '小组课',
         VIDEO_SHARE: '视频课',
         VIP_SHARE: '小酷Ai会员',
         MUSIC_SHARE: '曲谱',

+ 70 - 0
src/teacher/group-class/create-components/arrange.module.less

@@ -0,0 +1,70 @@
+.arrange {
+  margin: 0 14px;
+
+  .arrangeCell {
+    margin: 10px 0 0;
+    width: auto;
+    border-radius: 10px;
+    overflow: hidden;
+  }
+  .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;
+    }
+  }
+  .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%;
+      }
+    }
+  }
+}

+ 383 - 0
src/teacher/group-class/create-components/arrange.tsx

@@ -0,0 +1,383 @@
+import Calendar from '@/business-components/calendar'
+import request from '@/helpers/request'
+import { Button, Cell, Dialog, Popup, Sticky, Tag, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './arrange.module.less'
+import dayjs from 'dayjs'
+import { createState } from './createState'
+import { state } from '@/state'
+import { getWeekCh } from '@/helpers/utils'
+
+export default defineComponent({
+  name: 'arrange',
+  data() {
+    return {
+      selectStatus: false,
+      calendarList: {},
+      calendarDate: new Date() as Date // 日历当前时间
+    }
+  },
+  computed: {
+    showSelectList() {
+      let list = [...createState.selectCourseList]
+      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 createState.selectCourseList.length < createState.live.courseNum
+        ? 'noEnough'
+        : 'enough'
+    }
+  },
+  async mounted() {
+    const initDate = dayjs().add(1, 'day').toDate()
+    await this.getList(initDate)
+
+    if (createState.coursePlanStatus) {
+      this.selectStatus = true
+    }
+  },
+  methods: {
+    async getList(date?: Date) {
+      let params = {
+        day: dayjs(date || new Date()).format('DD'),
+        month: dayjs(date || new Date()).format('MM'),
+        year: dayjs(date || new Date()).format('YYYY')
+      }
+      try {
+        let res = await request.post(
+          '/api-teacher/courseSchedule/createLiveCourseCalendar',
+          {
+            data: {
+              ...params,
+              singleCourseMinutes: createState.live.singleMins,
+              freeCourseMinutes: createState.live.freeMinutes,
+              courseFreeMinutes: createState.live.freeMinutes,
+              teacherId: state.user.data?.userId,
+              courseType: "GROUP",
+            }
+          }
+        )
+        const result = res.data || []
+        let tempObj = {}
+        result.forEach((item: any) => {
+          tempObj[item.date] = item
+        })
+        this.calendarList = tempObj
+      } catch {}
+    },
+    onSelectDay(obj: any) {
+      const result = obj || []
+      let list = [...createState.selectCourseList]
+      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
+      })
+      createState.selectCourseList = [...tempList]
+    },
+    onCloseTag(item: any) {
+      Dialog.confirm({
+        title: '提示',
+        message: '您是否要删除该选择的课程?',
+        confirmButtonColor: 'var(--van-primary)'
+      }).then(() => {
+        const index = createState.selectCourseList.findIndex(
+          (course: any) => course.startTime === item.startTime
+        )
+        createState.selectCourseList.splice(index, 1)
+      })
+    },
+    async onSubmit() {
+      const groupId = this.$route.query.groupId
+      if (groupId){
+        createState.active = 4
+        return
+      }
+      if (createState.selectCourseList.length <= 0) {
+        Toast('请选择课程时间')
+        return
+      }
+
+      if (createState.selectCourseList.length < createState.live.courseNum) {
+        this.selectStatus = true
+        return
+      }
+
+      await this._lookCourse()
+    },
+    async _lookCourse(callBack?: Function) {
+      try {
+        let times = [] as any
+        createState.selectCourseList.forEach((item: any) => {
+          times.push({
+            startTime: item.startTime,
+            endTime: item.endTime
+          })
+        })
+        const res = await request.post(
+          '/api-teacher/courseGroup/lockCourseToCache',
+          {
+            data: {
+              courseNum: createState.live.courseNum,
+              courseFreeMinutes: createState.live.freeMinutes,
+              loop: this.selectType === 'noEnough' ? 1 : 0,
+              teacherId: state.user.data?.userId,
+              courseType: "GROUP",
+              timeList: [...times]
+            }
+          }
+        )
+        const result = res.data || []
+        result.forEach((item: any, index: number) => {
+          createState.live.coursePlanList[index] = {
+            ...createState.live.coursePlanList[index],
+            startTime: item.startTime,
+            endTime: item.endTime,
+            classNum: index + 1
+          }
+        })
+        createState.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())
+          createState.selectCourseList = []
+          this.selectStatus = false
+        })
+      }
+    },
+    async _unLookCourse() {
+      try {
+        await request.get('/api-teacher/courseGroup/unlockCourseToCache', {
+          params: {
+            teacherId: state.user.data?.userId,
+            courseType: "GROUP"
+          }
+        })
+        this.selectStatus = false
+        setTimeout(() => {
+          createState.live.coursePlanList.forEach((item: any) => {
+            item.startTime = ''
+            item.endTime = ''
+          })
+        }, 500)
+      } catch {}
+    },
+    async onReset() {
+      // 是否有锁课状态 或 是锁课类型的
+      if (createState.coursePlanStatus || this.selectType === 'enough') {
+        await this._unLookCourse()
+      } else if (this.selectType === 'noEnough') {
+        this.selectStatus = false
+      }
+      // 只能重置课程时间
+      createState.live.coursePlanList.forEach((item: any) => {
+        item.startTime = ''
+        item.endTime = ''
+      })
+
+      setTimeout(() => {
+        createState.coursePlanStatus = false
+      }, 500)
+    },
+    async onSure() {
+      // 判断是否有锁课状态 或 是锁课类型的 并且已经有课的
+      console.log(
+        this.selectType,
+        createState.coursePlanStatus,
+        createState.live.coursePlanList
+      )
+      let courseLength = 0
+      createState.live.coursePlanList.forEach((item: any) => {
+        item.startTime && courseLength++
+      })
+      if (this.selectType === 'enough' || courseLength > 0) {
+        this.selectStatus = false
+        createState.active = 4
+        return
+      }
+      const status = createState.coursePlanStatus
+      await this._lookCourse(() => {
+        if (status) {
+          this.selectStatus = false
+          createState.active = 4
+        }
+      })
+    }
+  },
+  render() {
+    const groupId = this.$route.query.groupId
+    return (
+      <div class={styles.arrange}>
+        {!groupId ? (
+          <>
+            <Calendar
+              selectList={createState.selectCourseList}
+              list={this.calendarList}
+              maxDays={createState.live.courseNum || 0}
+              nextMonth={(date: Date) => this.getList(date)}
+              prevMonth={(date: Date) => this.getList(date)}
+              selectDay={this.onSelectDay}
+              v-model:calendarDate={this.calendarDate}
+            />
+
+            <Cell
+              class={[styles.arrangeCell, 'mb12']}
+              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>
+          </>
+        ): (
+          <>
+            <Cell
+                class={[styles.arrangeCell, 'mb12']}
+                v-slots={{
+                  title: () => (
+                    <div class={styles.rTitle}>
+                      <span>已选择课程时间</span>
+                    </div>
+                  ),
+                }}
+            ></Cell>
+            {createState.live.coursePlanList.map(item => {
+              return (
+                <Cell title={`${item.startTime} ~ ${item.endTime}`}></Cell>
+              )
+            })}
+            <div class={['mb12']}></div>
+          </>
+        )}
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={['btnGroup', 'btnMore']}>
+            <Button
+              block
+              round
+              type="primary"
+              plain
+              onClick={() => {
+                createState.active = 2
+                // 重置选择的课次
+                createState.selectCourseList = []
+              }}
+            >
+              上一步
+            </Button>
+            <Button block round type="primary" onClick={this.onSubmit}>
+              下一步
+            </Button>
+          </div>
+        </Sticky>
+
+        <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' && !createState.coursePlanStatus
+                  ? '您所选择的上课时间未达到您输入的课时数,系统根据已选时间将自动按周顺延排课。'
+                  : '您已选择以下上课时间段,时间段会暂时锁定,锁定期间学员不可购买该时间段课程。'}
+              </p>
+              {createState.live.coursePlanList &&
+                createState.live.coursePlanList.length > 0 &&
+                createState.coursePlanStatus && (
+                  <p class={styles.times}>
+                    {createState.live.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>
+      </div>
+    )
+  }
+})

+ 7 - 0
src/teacher/group-class/create-components/course-plan.module.less

@@ -0,0 +1,7 @@
+.coursePlan {
+  .courseTime {
+    font-size: 14px;
+    color: #999999;
+    line-height: 20px;
+  }
+}

+ 91 - 0
src/teacher/group-class/create-components/course-plan.tsx

@@ -0,0 +1,91 @@
+import { Button, Field, Form, Sticky } from 'vant'
+import { defineComponent } from 'vue'
+import { basePlan, createState } from './createState'
+import styles from './course-plan.module.less'
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+
+export default defineComponent({
+  name: 'course-plan',
+  data() {
+    return {}
+  },
+  async mounted() {
+    let list = createState.live.coursePlanList
+    let listLength = list.length || 0
+    console.log(list)
+    if (createState.live.courseNum > listLength) {
+      for (let i = 0; i < createState.live.courseNum - listLength; i++) {
+        await this.addPlan()
+      }
+    } else if (createState.live.courseNum < listLength) {
+      for (let i = 0; i < listLength - createState.live.courseNum; i++) {
+        await this.delPlan()
+      }
+    }
+  },
+  methods: {
+    async addPlan() {
+      let list = createState.live.coursePlanList || []
+      list.push({
+        plan: '',
+        startTime: '',
+        endTime: '',
+        classNum: list.length + 1
+      })
+      createState.live.coursePlanList = list
+    },
+    async delPlan(index?: number) {
+      let list = createState.live.coursePlanList || []
+      list.splice(index || list.length - 1, 1)
+      createState.live.coursePlanList = list
+    }
+  },
+  render() {
+    return (
+      <Form class={styles.coursePlan} onSubmit={() => (createState.active = 3)}>
+        {/* scrollToError */}
+        {createState.live.coursePlanList &&
+          createState.live.coursePlanList.map((item: any) => (
+            <ColFieldGroup>
+              <ColField title={`第${item.classNum}课`} required border={false}>
+                <Field
+                  v-model={item.plan}
+                  name="plan"
+                  placeholder="请输入课程计划"
+                  rows="3"
+                  maxlength={200}
+                  showWordLimit
+                  autosize
+                  rules={[{ required: true, message: '请输入课程计划' }]}
+                  type="textarea"
+                />
+              </ColField>
+            </ColFieldGroup>
+          ))}
+
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={['btnGroup', 'btnMore']}>
+            <Button
+              block
+              round
+              type="primary"
+              plain
+              onClick={() => {
+                createState.active = 1
+                if (!createState.live.courseGroupId){ // 修改时不重置
+                  createState.live.coursePlanList = [{ ...basePlan }]
+                }
+              }}
+            >
+              上一步
+            </Button>
+            <Button block round type="primary" native-type="submit">
+              下一步
+            </Button>
+          </div>
+        </Sticky>
+      </Form>
+    )
+  }
+})

+ 74 - 0
src/teacher/group-class/create-components/course-start.module.less

@@ -0,0 +1,74 @@
+.courseStart {
+  .infoField {
+    width: 50vw;
+    font-size: 16px;
+
+    :global {
+      .van-tab {
+        font-size: 16px;
+      }
+      .van-tabs__nav--line {
+        padding-left: 0;
+      }
+      .van-tab--active {
+        color: #000;
+      }
+    }
+  }
+  .photoTip {
+    font-size: 14px;
+    color: #999999;
+    line-height: 27px;
+    padding: 5px 0;
+  }
+  .boxStyle {
+    background: transparent !important;
+    width: 18px;
+    height: 18px;
+    border: transparent !important;
+  }
+  :global {
+    .van-radio {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+    }
+    .van-radio__icon {
+      height: 18px;
+      line-height: 18px;
+      display: inline-block;
+      vertical-align: sub;
+    }
+    .van-radio__label {
+      line-height: 18px;
+    }
+  }
+  .imgContainer {
+    width: 150px;
+    height: 85px;
+    border-radius: 10px;
+    overflow: hidden;
+    margin: 0 0 12px;
+    position: relative;
+
+    :global {
+      .van-radio {
+        position: absolute;
+        bottom: 10px;
+        right: 20px;
+        z-index: 9;
+      }
+    }
+  }
+  .stepTips {
+    padding: 7px 12px;
+    margin-bottom: 15px;
+    background: linear-gradient(139deg, #fff6ee 0%, #ffecdd 100%);
+    border-radius: 6px;
+    line-height: 22px;
+    font-size: 12px;
+    color: #e0945a;
+    display: flex;
+    align-items: center;
+  }
+}

+ 335 - 0
src/teacher/group-class/create-components/course-start.tsx

@@ -0,0 +1,335 @@
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import {
+  Button,
+  Col,
+  Field,
+  Form,
+  Radio,
+  RadioGroup,
+  Row,
+  Sticky,
+  Tab,
+  Tabs,
+  Image,
+  Icon,
+  Popup,
+  DatetimePicker
+} from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './course-start.module.less'
+import ColUpload from '@/components/col-upload'
+import { verifiyNumberInteger } from '@/helpers/toolsValidate'
+import { formatterDate } from '@/helpers/utils'
+
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+import dayjs from 'dayjs'
+
+export default defineComponent({
+  name: 'course-start',
+  data() {
+    return {
+      typeDateTime: 'start',
+      dateStatus: false,
+      currentDate: new Date(),
+      minDate: dayjs().toDate(),
+      maxDate: new Date()
+    }
+  },
+  computed: {
+    disabled() {
+      return createState.live.courseGroupId ? true : false
+    }
+  },
+  mounted() {
+    if (createState.selectCourseList[0]?.startTime) {
+      this.maxDate = dayjs(createState.selectCourseList[0].startTime)
+        .subtract(1, 'day')
+        .toDate()
+    }
+
+    createState.live.salesStartDate =
+      createState.live.salesStartDate ||
+      dayjs(this.minDate).format('YYYY-MM-DD')
+    createState.live.salesEndDate =
+      createState.live.salesEndDate || dayjs(this.maxDate).format('YYYY-MM-DD')
+  },
+  methods: {
+    tabChange(name: number) {
+      createState.tabIndex = name
+    },
+    selectImg(val: string) {
+      createState.live.backgroundPic = ''
+      createState.live.backgroundPicTemplate = val
+    },
+    onFormatterInt(val: any) {
+      if (val) {
+        if( Number(val) >= (Number(createState.live.maxStudentNum) || 5)) {
+          return verifiyNumberInteger(createState.live.maxStudentNum || "5")
+        }
+        if(val >= 1) {
+          return verifiyNumberInteger(val)
+        }
+        return ""
+      } else {
+        return ''
+      }
+    },
+    onConfirm(val: any) {
+      if (this.typeDateTime === 'start') {
+        createState.live.salesStartDate = dayjs(val).format('YYYY-MM-DD')
+        if (
+          createState.live.salesEndDate &&
+          dayjs(createState.live.salesStartDate).isAfter(
+            dayjs(createState.live.salesEndDate)
+          )
+        ) {
+          createState.live.salesEndDate = ''
+        }
+      } else if (this.typeDateTime === 'end') {
+        createState.live.salesEndDate = dayjs(val).format('YYYY-MM-DD')
+      }
+      this.dateStatus = false
+    }
+  },
+  render() {
+    return (
+      <Form
+        class={styles.courseStart}
+        onSubmit={() => (createState.active = 5)}
+        scrollToError
+      >
+        <ColFieldGroup>
+          <ColField title="开售日期" required>
+            <Field
+              v-model={createState.live.salesStartDate}
+              name="salesStartDate"
+              readonly
+              isLink
+              placeholder="请选择开售日期"
+              disabled={this.disabled}
+              onClick={() => {
+                if (createState.live.courseGroupId) {
+                  return
+                }
+                this.minDate = dayjs().toDate()
+                this.currentDate = dayjs(
+                  createState.live.salesStartDate
+                ).toDate()
+                this.typeDateTime = 'start'
+                this.dateStatus = true
+              }}
+              rules={[{ required: true, message: '请选择开售日期' }]}
+            />
+          </ColField>
+          <ColField title="停售日期" required>
+            <Field
+              v-model={createState.live.salesEndDate}
+              name="salesEndDate"
+              readonly
+              isLink
+              disabled={this.disabled}
+              onClick={() => {
+                if (createState.live.courseGroupId) {
+                  return
+                }
+                this.minDate = dayjs(createState.live.salesStartDate).toDate()
+                this.currentDate = dayjs(createState.live.salesEndDate).toDate()
+                this.typeDateTime = 'end'
+                this.dateStatus = true
+              }}
+              rules={[{ required: true, message: '请选择停售日期' }]}
+              placeholder="请选择停售日期"
+            />
+          </ColField>
+        </ColFieldGroup>
+
+        <ColFieldGroup>
+          <ColField
+            title="最低开课人数"
+            required
+            style={{
+              marginBottom: '10px'
+            }}
+          >
+            <Field
+              v-model={createState.live.mixStudentNum}
+              name="mixStudentNum"
+              placeholder="请输入最低开课人数"
+              type="number"
+              maxlength={8}
+              disabled={this.disabled}
+              formatter={this.onFormatterInt}
+              rules={[{ required: true, message: '请输入最低开课人数' }]}
+              v-slots={{
+                button: () => <span>人</span>
+              }}
+            />
+          </ColField>
+          <div class={styles.stepTips}>
+            课程停售时付费学员达到该人数可开课,若未达到该人数课程将会失效,已付费学员将自动退款
+          </div>
+        </ColFieldGroup>
+
+        <ColFieldGroup>
+          <ColField
+            required
+            border={false}
+            v-slots={{
+              title: () => (
+                <Tabs
+                  v-model:active={createState.tabIndex}
+                  class={styles.infoField}
+                  onChange={this.tabChange}
+                  shrink
+                  color="var(--van-primary)"
+                  lineWidth={20}
+                >
+                  {/* <Tab title="图片模板" name={1}></Tab> */}
+                  <Tab title="自定义模板" name={2}></Tab>
+                </Tabs>
+              )
+            }}
+          >
+            <p class={styles.photoTip}>模板图片将作为该课程封面为学员展示</p>
+            {/* {createState.tabIndex === 1 ? ( */}
+            <Field
+              name="backgroundPicTemplate"
+              border={false}
+              v-show={createState.tabIndex === 1}
+              rules={[
+                {
+                  required:
+                    createState.tabIndex === 1 &&
+                    !createState.live.backgroundPic,
+                  message: '请选择图片模板'
+                }
+              ]}
+              v-slots={{
+                input: () => (
+                  <RadioGroup v-model={createState.live.backgroundPicTemplate}>
+                    <Row justify="space-between" style={{ width: '100%' }}>
+                      {createState.templateList.map((item: any) => (
+                        <Col
+                          span={12}
+                          class={styles.imgContainer}
+                          onClick={() => this.selectImg(item)}
+                        >
+                          <Image class={styles.imgContainer} src={item} />
+                          <Radio
+                            name={item}
+                            v-slots={{
+                              icon: (props: any) => (
+                                <Icon
+                                  class={styles.boxStyle}
+                                  name={
+                                    props.checked
+                                      ? activeButtonIcon
+                                      : inactiveButtonIcon
+                                  }
+                                  size="18"
+                                />
+                              )
+                            }}
+                          />
+                        </Col>
+                      ))}
+                    </Row>
+                  </RadioGroup>
+                )
+              }}
+            />
+            {/* ) : null} */}
+            {/* {createState.tabIndex == 2 ? ( */}
+            <Field
+              name="backgroundPic"
+              border={false}
+              v-show={createState.tabIndex == 2}
+              rules={[
+                {
+                  required: createState.tabIndex == 2,
+                  message: '请上传自定义模板'
+                }
+              ]}
+              v-slots={{
+                input: () => (
+                  <Row justify="space-between" style={{ width: '100%' }}>
+                    <Col span={12} class={styles.imgContainer}>
+                      <ColUpload
+                        cropper
+                        bucket="live-rewind"
+                        options={{
+                          fixedNumber: [1.77, 1],
+                          autoCropWidth: 750,
+                          autoCropHeight: 424
+                        }}
+                        onUploadChange={(val: any) => {
+                          if (val) {
+                            createState.live.backgroundPicTemplate = ''
+                          }
+                        }}
+                        v-model={createState.live.backgroundPic}
+                        class={styles.imgContainer}
+                      />
+                    </Col>
+                    <Col span={24}>
+                      <p
+                        class={styles.photoTip}
+                        style={{ color: '#ff4e19', padding: '0' }}
+                      >
+                        图片尺寸为750*424能达到最佳显示效果
+                      </p>
+                    </Col>
+                  </Row>
+                )
+              }}
+            />
+            {/* // ) : null} */}
+          </ColField>
+        </ColFieldGroup>
+
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={['btnGroup', 'btnMore']}>
+            <Button
+              block
+              round
+              type="primary"
+              plain
+              onClick={() => {
+                createState.active = 3
+                if (!createState.live.courseGroupId) {
+                  createState.live.salesStartDate = ''
+                  createState.live.salesEndDate = ''
+                  createState.live.backgroundPic = ''
+                  createState.live.backgroundPicTemplate = ''
+                  createState.live.mixStudentNum = null
+                }
+              }}
+            >
+              上一步
+            </Button>
+            <Button block round type="primary" native-type="submit">
+              下一步
+            </Button>
+          </div>
+        </Sticky>
+
+        <Popup show={this.dateStatus} position="bottom" round>
+          <DatetimePicker
+            type="date"
+            v-model={this.currentDate}
+            minDate={this.minDate}
+            maxDate={this.maxDate}
+            formatter={formatterDate}
+            onCancel={() => {
+              this.dateStatus = false
+            }}
+            onConfirm={this.onConfirm}
+          />
+        </Popup>
+      </Form>
+    )
+  }
+})

+ 17 - 0
src/teacher/group-class/create-components/course.module.less

@@ -0,0 +1,17 @@
+.classInfo {
+  .class-info-tip {
+    font-size: 14px;
+    color: #999999;
+    line-height: 27px;
+    padding: 0 14px 12px;
+
+    span {
+      color: #ff4e19;
+    }
+  }
+
+  .btnGroup {
+    padding: 0 14px;
+    padding-bottom: 15px;
+  }
+}

+ 263 - 0
src/teacher/group-class/create-components/course.tsx

@@ -0,0 +1,263 @@
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import ColPopup from '@/components/col-popup'
+import SubjectModel from '@/business-components/subject-list'
+import { ActionSheet, Button, Field, Form, Sticky } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './course.module.less'
+import { createState } from './createState'
+import {
+  verifiyNumberInteger,
+  verifyNumberIntegerAndFloat
+} from '@/helpers/toolsValidate'
+import request from '@/helpers/request'
+import { state } from '@/state'
+import ColHeader from '@/components/col-header'
+import TheSticky from '@/components/the-sticky'
+
+// 校验函数返回 true 表示校验通过,false 表示不通过
+export const validator = val => {
+  console.log(val)
+  if (Number(val) <= 0) {
+    return '课程组售价必须大于0'
+  } else {
+    return true
+  }
+}
+
+export default defineComponent({
+  name: 'course',
+  data() {
+    return {
+      subjectStatus: false,
+      classTimeStatus: false,
+      checked: false
+    }
+  },
+  computed: {
+    choiceSubjectIds() {
+      // 选择的科目编号
+      const ids = createState.live.subjectId
+        ? Number(createState.live.subjectId)
+        : null
+      return ids ? [ids] : []
+    },
+    subjectList() {
+      // 学科列表
+      return createState.subjectList || []
+    },
+    lessonSubjectName() {
+      // 选择的科目
+      let tempStr = ''
+      this.subjectList.forEach((item: any) => {
+        if (this.choiceSubjectIds.includes(item.id)) {
+          tempStr = item.name
+        }
+      })
+      return tempStr
+    },
+    calcSingleRatePrice() {
+      const rate = createState.rate || 0
+      const nums = createState.live.courseNum
+      const price = createState.live.coursePrice || 0
+      return nums ? ((price / nums) * (1 - rate / 100)).toFixed(2) : 0
+    },
+    calcRatePrice() {
+      // 计算手续费
+      const rate = createState.rate || 0
+      const price = createState.live.coursePrice || 0
+      return (price - (rate / 100) * price).toFixed(2)
+    },
+    disabled() {
+      return createState.live.courseGroupId ? true : false
+    }
+  },
+  async mounted() {
+    try {
+      const resVersion = await request.post('/api-teacher/open/appVersion', {
+        data: {
+          platform:
+            state.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher',
+          version: state.version
+        }
+      })
+      this.checked = resVersion.data.check ? true : false
+      // 审核版本金额默认为0
+      if (this.checked) {
+        createState.live.coursePrice = 0
+      }
+    } catch {
+      //
+    }
+  },
+  methods: {
+    onChoice(id: number) {
+      createState.live.subjectId = id
+      this.subjectStatus = false
+    },
+    onFormatter(val: any) {
+      return verifyNumberIntegerAndFloat(val)
+    },
+    onFormatterInt(val: any) {
+      if (val && val >= 1) {
+        return verifiyNumberInteger(val)
+      } else {
+        return ''
+      }
+    },
+    onSelect(action: any) {
+      createState.live.singleCourseMinutes =
+        Number(action.name || 0) + Number(action.freeMinutes || 0)
+      createState.live.singleMins = Number(action.name || 0)
+      createState.live.freeMinutes = Number(action.freeMinutes || 0)
+    }
+  },
+  render() {
+    return (
+      <Form
+        class={styles.classInfo}
+        onSubmit={() => (createState.active = 2)}
+        scrollToError
+      >
+        <ColFieldGroup>
+          <ColField title="课程名称" required>
+            <Field
+              v-model={createState.live.name}
+              name="name"
+              maxlength={20}
+              placeholder="请输入您的课程名称"
+              rules={[{ required: true, message: '请输入您的课程名称' }]}
+              disabled={this.disabled}
+            />
+          </ColField>
+          <ColField title="课程声部" required>
+            <Field
+              modelValue={this.lessonSubjectName}
+              name="subjectId"
+              readonly
+              isLink
+              disabled={this.disabled}
+              onClick={() => {
+                if (createState.live.courseGroupId) {
+                  return
+                }
+                this.subjectStatus = true
+              }}
+              rules={[{ required: true, message: '请选择课程声部' }]}
+              placeholder="请选择课程声部"
+            />
+          </ColField>
+
+          <ColField title="课程介绍" required border={false}>
+            <Field
+              v-model={createState.live.courseIntroduce}
+              name="courseIntroduce"
+              placeholder="请输入课程介绍"
+              rows="3"
+              maxlength={200}
+              showWordLimit
+              autosize
+              rules={[{ required: true, message: '请输入课程介绍' }]}
+              type="textarea"
+            />
+          </ColField>
+        </ColFieldGroup>
+
+        <ColFieldGroup>
+          <ColField title="课时数" required>
+            <Field
+              v-model={createState.live.courseNum}
+              name="courseNum"
+              placeholder="请输入您的课时数"
+              formatter={this.onFormatterInt}
+              type="number"
+              maxlength={2}
+              disabled={this.disabled}
+              rules={[{ required: true, message: '请输入您的课时数' }]}
+              v-slots={{
+                button: () => <span>课时</span>
+              }}
+            />
+          </ColField>
+          <ColField title="单课时时长" required>
+            <Field
+              modelValue={createState.live.singleMins}
+              name="singleMins"
+              readonly
+              disabled={this.disabled}
+              isLink
+              onClick={() => {
+                if (!createState.live.courseGroupId) {
+                  this.classTimeStatus = true
+                }
+              }}
+              rules={[{ required: true, message: '请选择单课时时长' }]}
+              placeholder="请选择单课时时长"
+            />
+          </ColField>
+          {/* 是审核版本才会显示金额 */}
+          {!this.checked && (
+            <ColField title="课程组售价" required>
+              <Field
+                v-model={createState.live.coursePrice}
+                name="coursePrice"
+                placeholder="请输入您的课程组售价"
+                formatter={this.onFormatter}
+                type="number"
+                maxlength={8}
+                disabled={this.disabled}
+                rules={[
+                  { required: true, validator, message: '请输入您的课程组售价' }
+                ]}
+                v-slots={{
+                  button: () => <span>元</span>
+                }}
+              />
+            </ColField>
+          )}
+        </ColFieldGroup>
+
+        {/* 是审核版本才会显示金额 */}
+        {!this.checked && (
+          <div class={styles['class-info-tip']}>
+            <p>扣除手续费后您的课程预计收入为:</p>
+            <p>
+              单课时<span>{this.calcSingleRatePrice}</span>元/人
+            </p>
+            <p>
+              课程组总收入<span>{this.calcRatePrice}</span>元/人
+            </p>
+            <p>您的课程收入将在课程结束后结算到您的账户中</p>
+          </div>
+        )}
+
+        <TheSticky position="bottom">
+          <div class={'btnGroup'}>
+            <Button block round type="primary" native-type="submit">
+              下一步
+            </Button>
+          </div>
+        </TheSticky>
+
+        <ColPopup v-model={this.subjectStatus}>
+          <ColHeader title="选择声部" />
+          <SubjectModel
+            selectType="Radio"
+            single
+            subjectList={createState.subjectList}
+            choiceSubjectIds={this.choiceSubjectIds}
+            onChoice={this.onChoice}
+          />
+        </ColPopup>
+
+        <ActionSheet
+          v-model:show={this.classTimeStatus}
+          actions={createState.minutes}
+          cancelText="取消"
+          closeOnClickAction
+          onSelect={this.onSelect}
+        />
+      </Form>
+    )
+  }
+})

+ 48 - 0
src/teacher/group-class/create-components/createState.ts

@@ -0,0 +1,48 @@
+import { reactive } from 'vue'
+
+export const basePlan = {
+  plan: '',
+  startTime: '',
+  endTime: '',
+  classNum: 1
+}
+
+export const createState = reactive({
+  subjectList: [], // 声部列表
+  active: 1,
+  rate: 0,
+  minutes: [] as any,
+  tabIndex: 1,
+  templateList: [
+    'https://oss.dayaedu.com/video-course/1657853010619green.png',
+    'https://oss.dayaedu.com/video-course/1657853051064gray.png',
+    'https://oss.dayaedu.com/video-course/1657853062314yellow.png',
+    'https://oss.dayaedu.com/video-course/1657853076136linear.png'
+  ], // 模板列表
+  selectCourseList: [] as any, // 选择课程列表
+  coursePlanStatus: false, // 是否有锁课程
+  live: {
+    maxStudentNum: "5", // 最大成课人数
+    teacherId: '',
+    courseGroupId: '',
+    courseType: 'GROUP',
+    name: '',
+    subjectId: null as any,
+    courseIntroduce: '',
+    courseNum: null as any,
+    singleCourseMinutes: 0,
+    singleMins: null as any,
+    freeMinutes: 0,
+    coursePrice: null as any,
+    salesStartDate: '',
+    salesEndDate: '',
+    mixStudentNum: null as any,
+    backgroundPic: '',
+    backgroundPicTemplate: '',
+    coursePlanList: [
+      {
+        ...basePlan
+      }
+    ]
+  }
+})

+ 2 - 0
src/teacher/group-class/create-components/detail.module.less

@@ -0,0 +1,2 @@
+.detail {
+}

+ 186 - 0
src/teacher/group-class/create-components/detail.tsx

@@ -0,0 +1,186 @@
+import CoursePlanStep from '@/business-components/course-plan-step'
+import SectionDetail from '@/business-components/section-detail'
+import UserDetail from '@/business-components/user-detail'
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { Button, Dialog, Sticky, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './detail.module.less'
+import dayjs from 'dayjs'
+import { postMessage } from '@/helpers/native-message'
+interface IProps {
+  courseTime: string
+  coursePlan: string
+  videoPosterUrl?: string
+  roomUid?: string
+  liveState?: number
+  id?: number | string
+}
+export default defineComponent({
+  name: 'detail',
+  computed: {
+    userInfo() {
+      const startTime = createState.live.coursePlanList[0].startTime
+      const endTime = createState.live.coursePlanList[0].endTime
+      return {
+        headUrl: state.user.data?.heardUrl,
+        username:
+          state.user.data?.username || `游客${state.user.data?.userId || ''}`,
+        startTime:
+          `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(startTime).format(
+            'HH:mm'
+          )}~${dayjs(endTime).format('HH:mm')}` || '',
+        buyNum: 0,
+        type: 'group',
+        mixStudentNum: createState.live.mixStudentNum,
+        lessonPrice: createState.live.coursePrice,
+        lessonNum: createState.live.courseNum,
+        lessonDesc: createState.live.courseIntroduce,
+        lessonCoverUrl:
+          createState.live.backgroundPic ||
+          createState.live.backgroundPicTemplate,
+        lessonName: createState.live.name,
+        auditVersion:0
+      }
+    },
+    courseInfo() {
+      const tempArr = [] as IProps[]
+      const coursePlanList = createState.live.coursePlanList || []
+      coursePlanList.forEach((item: any) => {
+        tempArr.push({
+          courseTime: `${dayjs(item.startTime).format('YYYY-MM-DD')} ${dayjs(
+            item.startTime
+          ).format('HH:mm')}~${dayjs(item.endTime).format('HH:mm')}`,
+          roomUid: item.roomUid,
+          liveState: item.liveState,
+          coursePlan: item.plan,
+          id: item.courseId
+        })
+      })
+      return tempArr || []
+    }
+  },
+  data() {
+    return {
+      submitLoading: false
+    }
+  },
+  methods: {
+    async onSubmit() {
+      if(this.submitLoading) return
+      this.submitLoading = true;
+      
+      try {
+        const params = {
+          ...createState.live,
+          startTime: createState.live.coursePlanList[0].startTime,
+          backgroundPic:
+            createState.live.backgroundPic ||
+            createState.live.backgroundPicTemplate,
+          teacherId: state.user.data?.userId,
+          version: state.version,
+          platform: state.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher',
+        }
+        await request.post('/api-teacher/courseGroup/addLiveCourse', {
+          data: params
+        })
+        Toast.success('创建成功')
+        setTimeout(() => {
+          postMessage({ api: 'back' })
+        }, 1000)
+      } catch (e: any) {
+        // 报错时需要重置日历表的数据
+        const message = e.message
+        Dialog.alert({
+          title: '提示',
+          confirmButtonColor: 'var(--van-primary)',
+          message
+        }).then(() => {
+          createState.active = 3
+          createState.selectCourseList = []
+          createState.live.salesStartDate = ''
+          createState.live.salesEndDate = ''
+          createState.live.mixStudentNum = null
+          createState.live.backgroundPic = ''
+          createState.live.backgroundPicTemplate = ''
+          createState.coursePlanStatus = false
+        })
+      }
+      this.submitLoading = false;
+    },
+
+    async onUpdate() {
+      if(this.submitLoading) return
+      this.submitLoading = true;
+      
+      const params = {
+        id: createState.live.courseGroupId,
+        ...createState.live,
+        startTime: createState.live.coursePlanList[0].startTime,
+        backgroundPic:
+          createState.live.backgroundPic ||
+          createState.live.backgroundPicTemplate,
+      }
+      console.log({...params})
+      await request.post('/api-teacher/courseGroup/updateLiveCourse', {
+        data: params
+      })
+      this.submitLoading = false
+      Toast({
+        type: 'success',
+        message: '编辑成功',
+        duration: 1000,
+        onClose: () => {
+          postMessage({ api: 'back' })
+        }
+      })
+    }
+  },
+  render() {
+    return (
+      <div class={[styles['detail']]}>
+        <UserDetail userInfo={this.userInfo} />
+        <SectionDetail>
+          <p class={styles.introduction}>{this.userInfo.lessonDesc}</p>
+        </SectionDetail>
+
+        <SectionDetail
+          title="课程安排"
+          icon="courseList"
+          titleShow={false}
+          class={'mb12'}
+          contentStyle={{ paddingTop: '0' }}
+        >
+          <CoursePlanStep courseInfo={this.courseInfo} />
+        </SectionDetail>
+
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={['btnGroup', 'btnMore']}>
+            <Button
+              block
+              round
+              type="primary"
+              plain
+              onClick={() => {
+                createState.active = 4
+              }}
+            >
+              返回编辑
+            </Button>
+            {createState.live.courseGroupId ? (
+              <Button block round type="primary" disabled={this.submitLoading} onClick={this.onUpdate}>
+                确认修改
+              </Button>
+            ) : (
+              <Button block round type="primary" disabled={this.submitLoading} onClick={this.onSubmit}>
+                创建成功
+              </Button>
+            )}
+            
+          </div>
+        </Sticky>
+      </div>
+    )
+  }
+})

+ 32 - 0
src/teacher/group-class/create-components/steps.module.less

@@ -0,0 +1,32 @@
+.steps {
+  padding-top: 15px;
+  padding-left: 14px;
+  .gridName {
+    font-size: 14px;
+    font-weight: 500;
+    color: #b4b4b4;
+    line-height: 20px;
+    padding-top: 4px;
+    &.active {
+      color: var(--van-primary);
+    }
+  }
+  :global {
+    .van-grid-item {
+      padding-right: 14px;
+      &:first-child {
+        padding-right: 10px;
+      }
+    }
+    .van-grid-item__content {
+      padding-top: 7px;
+      padding-bottom: 7px;
+      border-radius: 10px;
+      overflow: hidden;
+    }
+    .van-badge__wrapper {
+      display: flex;
+      align-items: center;
+    }
+  }
+}

+ 112 - 0
src/teacher/group-class/create-components/steps.tsx

@@ -0,0 +1,112 @@
+import { Grid, GridItem, Icon } from 'vant'
+import { defineComponent } from 'vue'
+import { createState } from './createState'
+import styles from './steps.module.less'
+
+export const getAssetsHomeFile = (fileName: string) => {
+  const path = `../images/${fileName}`
+  const modules = import.meta.globEager('../images/*')
+  return modules[path].default
+}
+
+export default defineComponent({
+  name: 'steps',
+  render() {
+    return (
+      <Grid class={styles.steps} border={false} columnNum="4">
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={getAssetsHomeFile('icon_course_active.png')}
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 1 ? styles.active : null
+                  ]}
+                >
+                  课程信息
+                </span>
+              </>
+            )
+          }}
+        />
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={
+                    createState.active >= 2
+                      ? getAssetsHomeFile('icon_plan_active.png')
+                      : getAssetsHomeFile('icon_plan_default.png')
+                  }
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 2 ? styles.active : null
+                  ]}
+                >
+                  教学计划
+                </span>
+              </>
+            )
+          }}
+        />
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={
+                    createState.active >= 3
+                      ? getAssetsHomeFile('icon_arrange_active.png')
+                      : getAssetsHomeFile('icon_arrange_default.png')
+                  }
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 3 ? styles.active : null
+                  ]}
+                >
+                  课程安排
+                </span>
+              </>
+            )
+          }}
+        />
+        <GridItem
+          v-slots={{
+            default: () => (
+              <>
+                <Icon
+                  name={
+                    createState.active >= 4
+                      ? getAssetsHomeFile('icon_start_active.png')
+                      : getAssetsHomeFile('icon_start_default.png')
+                  }
+                  size={24}
+                />
+                <span
+                  class={[
+                    styles.gridName,
+                    createState.active >= 4 ? styles.active : null
+                  ]}
+                >
+                  开课条件
+                </span>
+              </>
+            )
+          }}
+        />
+      </Grid>
+    )
+  }
+})

+ 21 - 0
src/teacher/group-class/create.module.less

@@ -0,0 +1,21 @@
+.live-create {
+  :global(.van-sticky--fixed) {
+    // box-shadow: 10px 10px 10px var(--box-shadow-color);
+  }
+}
+.iconQuestion {
+  width: 24px;
+  height: 24px;
+  flex-shrink: 0;
+}
+
+.tipPopup {
+  background: transparent;
+
+  :global {
+    .van-popup__close-icon {
+      font-size: 18px;
+      color: #CCCCCC;
+    }
+  }
+}

+ 143 - 0
src/teacher/group-class/create.tsx

@@ -0,0 +1,143 @@
+import { defineComponent } from 'vue'
+import Steps from './create-components/steps'
+import { createState } from './create-components/createState'
+import Course from './create-components/course'
+import CoursePlan from './create-components/course-plan'
+import CourseStart from './create-components/course-start'
+import Detail from './create-components/detail'
+import { Popup, Sticky } from 'vant'
+import Arrange from './create-components/arrange'
+import request from '@/helpers/request'
+import styles from './create.module.less'
+import ColHeader from '@/components/col-header'
+import TheSticky from '@/components/the-sticky'
+import iconQuestion from '@/teacher/practice-class/tip-model/images/icon-question.png'
+import TipModel from '../practice-class/tip-model'
+
+export default defineComponent({
+  name: 'LiveCreate',
+  data() {
+    return {
+      tipStatus: false
+    }
+  },
+  async mounted() {
+    try {
+      // 获取手续费和分钟数
+      const config = await request.get(
+        '/api-teacher/sysConfig/queryByParamNameList',
+        {
+          params: {
+            paramNames: 'group_service_rate,group_time_setting,group_max_student_num'
+          }
+        }
+      )
+      const configData = config.data || []
+      configData.forEach((item: any) => {
+        if (item.paramName === 'group_time_setting') {
+          const mins = item.paramValue ? JSON.parse(item.paramValue) : []
+          const tempArr = [] as any
+          mins.forEach((item: any) => {
+            tempArr.push({
+              ...item,
+              name: item.courseMinutes
+            })
+          })
+          createState.minutes = [...tempArr]
+        }
+        if (item.paramName === 'group_service_rate') {
+          createState.rate = item.paramValue
+        }
+        // 最大成课人数
+        if(item.paramName === "group_max_student_num") {
+          createState.live.maxStudentNum = item.paramValue
+        }
+      })
+
+      const teacher = await request.post('/api-teacher/teacher/querySubject')
+      createState.subjectList = teacher.data || []
+    } catch (err: any) {
+      console.log(err)
+    }
+    this.getLiveClassDetail()
+  },
+  methods: {
+    // 获取直播课详情
+    async getLiveClassDetail() {
+      const groupId = this.$route.query.groupId
+      if (!groupId) return
+      const res = await request.get(
+        `/api-teacher/courseGroup/queryLiveCourseInfo?groupId=${groupId}`
+      )
+      console.log(res, createState)
+      if (res.code == 200) {
+        const data = res.data
+        createState.live.courseGroupId = data.courseGroupId
+        createState.live.teacherId = data.teacherId
+        createState.live.name = data.courseGroupName
+        createState.live.subjectId =
+          (
+            createState.subjectList.find(
+              (n: any) => n.name === data.subjectName
+            ) as any
+          )?.id || ''
+        createState.live.courseIntroduce = data.courseIntroduce
+        createState.live.courseNum = data.courseNum
+        createState.live.singleMins = data.singleCourseMinutes
+        createState.live.coursePrice = data.coursePrice
+        createState.live.coursePlanList = data.planList
+        createState.live.salesStartDate = data.salesStartDate
+        createState.live.salesEndDate = data.salesEndDate
+        createState.live.mixStudentNum = data.mixStudentNum
+        createState.live.backgroundPic = data.backgroundPic
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles['live-create']}>
+        <TheSticky position="top">
+          <ColHeader border={false} v-slots={{
+                right: () => (
+                  <img
+                    src={iconQuestion}
+                    class={styles.iconQuestion}
+                    onClick={() => (this.tipStatus = true)}
+                  />
+                )
+              }} />
+          {createState.active !== 5 && (
+            <Steps
+              style={{ backgroundColor: '#f6f8f9', paddingBottom: '12px' }}
+            />
+          )}
+        </TheSticky>
+
+        {createState.active === 1 && <Course />}
+
+        {createState.active == 2 && <CoursePlan />}
+
+        {createState.active == 3 && <Arrange />}
+
+        {createState.active == 4 && <CourseStart />}
+
+        {createState.active == 5 && <Detail />}
+
+        <Popup
+          show={this.tipStatus}
+          class={styles.tipPopup}
+          closeable
+          onClose={() => (this.tipStatus = false)}
+        >
+          <TipModel
+            onClose={() => (this.tipStatus = false)}
+            title={'什么是小组课?'}
+            content={
+              '小组课是老师根据教学目的编排的课程,固定时间进行1V5线上授课。您可根据老师开放的课程内容和您的时间安排,选择您感兴趣的课程组进行学习。'
+            }
+          />
+        </Popup>
+      </div>
+    )
+  }
+})

+ 22 - 0
src/teacher/group-class/group-detail.module.less

@@ -0,0 +1,22 @@
+.live-detail {
+  .introduction {
+    color: #7a7a7a;
+    line-height: 23px;
+    padding-bottom: 8px;
+  }
+
+  :global {
+    .van-tabs__wrap {
+      margin-bottom: 15px;
+    }
+  }
+
+  .shareCourse {
+    margin: 0;
+    padding: 8px;
+    background: #fff;
+    :global(.itemTitle) {
+      max-width: 110px !important;
+    }
+  }
+}

+ 331 - 0
src/teacher/group-class/group-detail.tsx

@@ -0,0 +1,331 @@
+import CoursePlanStep from '@/business-components/course-plan-step'
+import SectionDetail from '@/business-components/section-detail'
+import UserDetail from '@/business-components/user-detail'
+import UserList from '@/business-components/user-list'
+import ColResult from '@/components/col-result'
+import ColShare from '@/components/col-share'
+import ColSticky from '@/components/col-sticky'
+import { postMessage } from '@/helpers/native-message'
+import request from '@/helpers/request'
+import { state } from '@/state'
+import LiveItem from '@/views/live-class/live-item'
+import dayjs from 'dayjs'
+import { Button, Popup, Tab, Tabs, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './group-detail.module.less'
+import ColHeader from '@/components/col-header'
+interface IProps {
+  courseTime: string
+  coursePlan: string
+  videoPosterUrl?: string
+  roomUid?: string
+  liveState?: number
+  id?: number | string
+}
+export default defineComponent({
+  name: 'GroupDetail',
+  data() {
+    const query = this.$route.query
+    return {
+      share: query.share,
+      joinRoom: query.joinRoom, // 原生传递过来的参数,判断是否进入直播间
+      groupId: query.groupId,
+      courseId: query.classId,
+      live: {} as any,
+      shareStatus: false,
+      shareUrl: '',
+      myself: false
+    }
+  },
+  computed: {
+    userInfo() {
+      const live = this.live as any
+      const planList = live.planList || []
+      const startTime = planList[0]?.startTime || new Date()
+      const endTime = planList[0]?.endTime || new Date()
+      const studentNum = live.maxStudentNum || 0 - live.studentCount || 0
+      return {
+        headUrl: live.avatar,
+        avatar: live.avatar,
+        username: live.userName,
+        id: live.teacherId,
+        startTime:
+          `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(startTime).format(
+            'HH:mm'
+          )}~${dayjs(endTime).format('HH:mm')}` || '',
+        lessonPrice: live.coursePrice,
+        buyNum: live.studentCount || 0,
+        type: 'group',
+        mixStudentNum: studentNum > 0 ? studentNum : 0,
+        lessonId: live.courseGroupId,
+        lessonNum: live.courseNum || 0, // 课时数
+        lessonDesc: live.courseIntroduce,
+        lessonCoverUrl: live.backgroundPic || live.backgroundPicTemplate,
+        lessonName: live.courseGroupName,
+        subjectName: live.subjectName,
+        courseStartTime: live.courseStartTime,
+        auditVersion: live.auditVersion || 0,
+        isDegree: live.degreeFlag ? true : false,
+        isTeacher: live.teacherFlag ? true : false
+      }
+    },
+    courseInfo() {
+      const tempArr = [] as IProps[]
+      const coursePlanList = this.live.planList || []
+      coursePlanList.forEach((item: any) => {
+        const startTime = item.startTime || new Date()
+        const endTime = item.endTime || new Date()
+        tempArr.push({
+          courseTime: `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(
+            startTime
+          ).format('HH:mm')}~${dayjs(endTime).format('HH:mm')}`,
+          coursePlan: item.plan,
+          roomUid: item.roomUid,
+          liveState: item.liveState,
+          id: item.courseId
+        })
+      })
+      return tempArr || []
+    },
+    liveStatus() {
+      const coursePlanList = this.live.planList || []
+      const tempObj = {
+        status: false,
+        liveStatus: 0,
+        roomUid: ''
+      }
+      coursePlanList.forEach((item: any) => {
+        if (item.courseId === Number(this.courseId)) {
+          tempObj.status = true
+          tempObj.liveStatus = item.liveStatus
+          tempObj.roomUid = item.roomUid
+        }
+      })
+      return tempObj
+    },
+    studentList() {
+      const live = this.live as any
+      return live.studentList || []
+    },
+    courseOffStatus() {
+      const live = this.live as any
+      let status = false
+      if (
+        (live.status === 'APPLY' && live.studentList.length === 0) ||
+        live.status === 'NOT_SALE'
+      ) {
+        status = true
+      }
+      return status
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.get(
+        '/api-teacher/courseGroup/queryLiveCourseInfo',
+        {
+          params: {
+            groupId: this.groupId
+          }
+        }
+      )
+      this.live = res.data || {}
+
+      if (state.platformType === 'TEACHER') {
+        this.myself = !res.data.myself
+      }
+
+      this.shareUrl = `${location.origin}/teacher/#/shareLive?recomUserId=${state.user.data?.userId}&groupId=${this.groupId}&p=tenant`
+      // console.log(this.live)
+    } catch {
+      //
+    }
+  },
+  methods: {
+    async onJoinRoom() {
+      try {
+        const res = await request.get(
+          '/api-teacher/courseGroup/queryLiveCourseInfo',
+          {
+            params: {
+              groupId: this.groupId
+            }
+          }
+        )
+        const result = res.data || {}
+        const coursePlanList = result.planList || []
+        let tempObj: any = {}
+        coursePlanList.forEach((item: any) => {
+          if (item.courseId === Number(this.courseId)) {
+            tempObj = item
+          }
+        })
+        if (tempObj && tempObj.liveState === 1) {
+          postMessage({
+            api: 'joinLiveRoom',
+            content: {
+              roomId: tempObj.roomUid,
+              teacherId: this.live.teacherId
+            }
+          })
+        } else if (tempObj && tempObj.liveState === 2) {
+          setTimeout(() => {
+            Toast('课程已结束')
+          }, 100)
+        } else {
+          setTimeout(() => {
+            Toast('课程尚未开始,请耐心等候')
+          }, 100)
+        }
+      } catch {
+        //
+      }
+    },
+    async cancelCourseGroup() {
+      try {
+        await request.get('/api-teacher/courseGroup/cancelCourseGroup', {
+          params: {
+            groupId: this.groupId
+          }
+        })
+        Toast('取消课程成功')
+        setTimeout(() => {
+          postMessage({ api: 'back', content: {} })
+        }, 500)
+      } catch {
+        //
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={[styles['live-detail'], 'mb12']}>
+        <ColHeader />
+        <UserDetail userInfo={this.userInfo} />
+        <SectionDetail border>
+          <p class={styles.introduction}>{this.userInfo.lessonDesc}</p>
+        </SectionDetail>
+
+        {this.myself ? (
+          <SectionDetail title="课程列表" icon="courseList" border={true}>
+            <CoursePlanStep
+              courseInfo={this.courseInfo}
+              courseId={Number(this.courseId) || 0}
+            />
+          </SectionDetail>
+        ) : (
+          <SectionDetail
+            title="课程列表"
+            icon="courseList"
+            titleShow={false}
+            contentStyle={{ paddingTop: '0' }}
+          >
+            <Tabs color="var(--van-primary)" lineWidth={20} sticky>
+              <Tab title="课程" titleClass="van-hairline--bottom">
+                <CoursePlanStep
+                  courseInfo={this.courseInfo}
+                  courseId={Number(this.courseId) || 0}
+                />
+              </Tab>
+              <Tab title="学员列表" titleClass="van-hairline--bottom">
+                {this.studentList.map((item: any) => (
+                  <UserList
+                    class="mb12"
+                    users={{
+                      avatar: item.avatar,
+                      studentId: item.studentId,
+                      studentName: item.userName,
+                      createTime: item.createTime
+                    }}
+                  />
+                ))}
+                {this.studentList.length === 0 && (
+                  <ColResult
+                    tips="暂无购买学员"
+                    classImgSize="SMALL"
+                    btnStatus={false}
+                  />
+                )}
+              </Tab>
+            </Tabs>
+          </SectionDetail>
+        )}
+
+        {this.live.status !== 'OUT_SALE' && (
+          <>
+            {this.courseOffStatus && (
+              <ColSticky position="bottom" background="white">
+                <div class={['btnGroup']} style={{ paddingTop: '12px' }}>
+                  <Button
+                    block
+                    round
+                    type="primary"
+                    onClick={this.cancelCourseGroup}
+                  >
+                    取消课程
+                  </Button>
+                </div>
+              </ColSticky>
+            )}
+
+            {this.joinRoom == '1' && this.liveStatus.liveStatus !== 2 && (
+              <ColSticky position="bottom" background="white">
+                <div class={['btnGroup']} style={{ paddingTop: '12px' }}>
+                  <Button block round type="primary" onClick={this.onJoinRoom}>
+                    进入直播间
+                  </Button>
+                </div>
+              </ColSticky>
+            )}
+
+            {this.share == '1' && this.courseInfo.length > 0 && (
+              <ColSticky position="bottom" background="white">
+                <div class={['btnGroup']} style={{ paddingTop: '12px' }}>
+                  <Button
+                    block
+                    round
+                    type="primary"
+                    onClick={() => {
+                      this.shareStatus = true
+                    }}
+                  >
+                    分享
+                  </Button>
+                </div>
+              </ColSticky>
+            )}
+          </>
+        )}
+
+        <Popup
+          v-model:show={this.shareStatus}
+          style={{ background: 'transparent' }}
+        >
+          <ColShare
+            teacherId={this.userInfo.id}
+            shareUrl={this.shareUrl}
+            shareType="live"
+          >
+            <LiveItem
+              class={styles.shareCourse}
+              liveInfo={{
+                backgroundPic: this.userInfo.lessonCoverUrl,
+                courseGroupId: this.userInfo.lessonId,
+                courseGroupName: this.userInfo.lessonName,
+                courseNum: this.userInfo.lessonNum,
+                coursePrice: this.userInfo.lessonPrice,
+                teacherName: this.userInfo.username,
+                teacherId: this.userInfo.id,
+                avatar: this.userInfo.avatar,
+                studentCount: this.userInfo.buyNum,
+                courseStartTime: this.userInfo.courseStartTime,
+                existBuy: 0,
+                subjectName: this.userInfo.subjectName
+              }}
+            />
+          </ColShare>
+        </Popup>
+      </div>
+    )
+  }
+})

BIN
src/teacher/group-class/images/icon_arrange_active.png


BIN
src/teacher/group-class/images/icon_arrange_default.png


BIN
src/teacher/group-class/images/icon_course_active.png


BIN
src/teacher/group-class/images/icon_plan_active.png


BIN
src/teacher/group-class/images/icon_plan_default.png


BIN
src/teacher/group-class/images/icon_start_active.png


BIN
src/teacher/group-class/images/icon_start_default.png


+ 38 - 16
src/teacher/income-consus/echarts.ts

@@ -1,5 +1,3 @@
-import { position } from 'html2canvas/dist/types/css/property-descriptors/position'
-
 export const lineChartOption = {
   legend: { show: false },
   emphasis: { lineStyle: { width: 2 } },
@@ -23,8 +21,10 @@ export const lineChartOption = {
     axisLine: { lineStyle: { color: '#8C8C8C' } }
   },
   color: [
+    '#FFCC80',
     '#5B8FF9',
     '#2DC7AA',
+    '#FF602C',
     '#91DD1C',
     '#FFA92C',
     '#BE7E2E',
@@ -32,8 +32,7 @@ export const lineChartOption = {
     '#D22CFF',
     '#FF3C3C',
     '#1AEE3E',
-    '#00c9ff',
-    '#7c47ff'
+    '#00c9ff'
   ],
   series: [
     {
@@ -53,6 +52,27 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
+      name: 'VIP定制课',
+      type: 'line',
+      emphasis: { lineStyle: { width: 1 } }
+    },
+    {
+      lineStyle: { width: 1 },
+      data: [
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00',
+        '0.00'
+      ],
+      symbol: 'circle',
       name: '趣纠课',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
@@ -95,7 +115,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '视频课',
+      name: '小组课',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -116,7 +136,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '乐谱',
+      name: '视频课',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -137,7 +157,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '小酷Ai推广',
+      name: '乐谱',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -158,7 +178,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '直播课推荐',
+      name: '小酷Ai推广',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -179,7 +199,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '视频课推荐',
+      name: '直播课推荐',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -200,7 +220,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '商品推荐',
+      name: '视频课推荐',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -221,7 +241,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '乐谱推荐',
+      name: '商品推荐',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -242,7 +262,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '专辑推荐',
+      name: '乐谱推荐',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -263,7 +283,7 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: '活动报名',
+      name: '专辑推荐',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
     },
@@ -284,10 +304,10 @@ export const lineChartOption = {
         '0.00'
       ],
       symbol: 'circle',
-      name: 'VIP定制课',
+      name: '活动报名',
       type: 'line',
       emphasis: { lineStyle: { width: 1 } }
-    },
+    }
   ],
   title: { show: false },
   grid: {
@@ -378,6 +398,7 @@ export const pieChartOption = {
         { name: '专辑推荐', value: '0.00' },
         { name: '活动报名', value: '0.00' },
         { name: 'VIP定制课', value: '0.00' },
+        { name: '小组课', value: '0.00' },
       ],
       type: 'pie',
       radius: ['50%', '80%']
@@ -403,6 +424,7 @@ export const pieChartOption = {
     '#1AEE3E',
     '#00c9ff',
     '#7c47ff',
-    '#FF6600'
+    '#FFCF78',
+    '#FF6632',
   ]
 }

+ 9 - 3
src/teacher/income-consus/index.module.less

@@ -92,8 +92,11 @@
   .color10 {
     background: #00c9ff;
   }
-  .color10 {
-    background: #FF6600;
+  .color11 {
+    background: linear-gradient( 180deg, #FFEA2C 0%, #FFCC80 100%);
+  }
+  .color12 {
+    background: linear-gradient( 153deg, #FF8953 0%, #FF602C 100%);
   }
 
   .type {
@@ -218,6 +221,9 @@
   .pieLive {
     background: #2dc7aa;
   }
+  .pieGroup {
+    background: #FF6632;
+  }
   .pieVideo {
     background: #91dd1c;
   }
@@ -246,7 +252,7 @@
     background: #00c9ff !important;
   }
   .pie8 {
-    background: #FF6600 !important;
+    background: #FFCF78 !important;
   }
   .pieTitle {
     display: inline-block;

+ 38 - 14
src/teacher/income-consus/index.tsx

@@ -104,6 +104,8 @@ export default defineComponent({
         vipCourseRate: 0,
         liveAmount: 0,
         liveRate: 0,
+        groupAmount: 0,
+        groupRate: 0,
         videoAmount: 0,
         videoRate: 0,
         musicAmount: 0,
@@ -158,7 +160,8 @@ export default defineComponent({
           totalSingleRate: Number(
             (
               result.practiceRate +
-              result.liveRate +
+              result.liveRate + 
+              result.groupRate || 0 + 
               result.videoRate +
               result.musicRate + 
               result.vipCourseRate
@@ -182,6 +185,8 @@ export default defineComponent({
           vipCourseRate: result.vipCourseRate || 0,
           liveAmount: result.liveAmount || 0,
           liveRate: result.liveRate || 0,
+          groupAmount: result.groupAmount || 0,
+          groupRate: result.groupRate || 0,
           videoAmount: result.videoAmount || 0,
           videoRate: result.videoRate || 0,
           musicAmount: result.musicAmount || 0,
@@ -208,6 +213,7 @@ export default defineComponent({
           practiceAmount: [] as any,
           vipCourseAmount: [] as any,
           liveAmount: [] as any,
+          groupAmount: [] as any,
           videoAmount: [] as any,
           musicAmount: [] as any,
           vipShareAmount: [] as any,
@@ -235,22 +241,25 @@ export default defineComponent({
           lineData.musicShareAmount.push(item.musicShareAmount)
           lineData.albumShareAmount.push(item.albumShareAmount || 0)
           lineData.actiRegistShareAmount.push(item.actiRegistShareAmount)
-          lineData.vipCourseAmount.push(item.vipCourseAmount)
+          lineData.vipCourseAmount.push(item.vipCourseAmount || 0)
+          lineData.groupAmount.push(item.groupAmount || 0)
         })
         // 初始化折线图
         lineChartOption.xAxis.data = lineData.xAxis
-        lineChartOption.series[0].data = lineData.practiceAmount
-        lineChartOption.series[1].data = lineData.liveAmount
-        lineChartOption.series[2].data = lineData.videoAmount
-        lineChartOption.series[3].data = lineData.musicAmount
-        lineChartOption.series[4].data = lineData.vipShareAmount
-        lineChartOption.series[5].data = lineData.liveShareAmount
-        lineChartOption.series[6].data = lineData.videoShareAmount
-        lineChartOption.series[7].data = lineData.mallShareAmount
-        lineChartOption.series[8].data = lineData.musicShareAmount
-        lineChartOption.series[9].data = lineData.albumShareAmount
-        lineChartOption.series[10].data = lineData.actiRegistShareAmount
-        lineChartOption.series[11].data = lineData.vipCourseAmount
+        lineChartOption.series[0].data = lineData.vipCourseAmount
+        lineChartOption.series[1].data = lineData.practiceAmount
+        lineChartOption.series[2].data = lineData.liveAmount
+        lineChartOption.series[3].data = lineData.groupAmount
+        lineChartOption.series[4].data = lineData.videoAmount
+        lineChartOption.series[5].data = lineData.musicAmount
+        lineChartOption.series[6].data = lineData.vipShareAmount
+        lineChartOption.series[7].data = lineData.liveShareAmount
+        lineChartOption.series[8].data = lineData.videoShareAmount
+        lineChartOption.series[9].data = lineData.mallShareAmount
+        lineChartOption.series[10].data = lineData.musicShareAmount
+        lineChartOption.series[11].data = lineData.albumShareAmount
+        lineChartOption.series[12].data = lineData.actiRegistShareAmount
+        
         // console.log(lineChartOption)
         this.myChart.clear()
         this.myChart.setOption(lineChartOption)
@@ -268,6 +277,7 @@ export default defineComponent({
         pieChartOption.series[0].data[9].value = result.albumShareAmount
         pieChartOption.series[0].data[10].value = result.actiRegistShareAmount
         pieChartOption.series[0].data[11].value = result.vipCourseAmount
+        pieChartOption.series[0].data[12].value = result.groupAmount || 0
         console.log(pieChartOption)
         this.myChart2.clear()
         this.myChart2.setOption(pieChartOption)
@@ -331,6 +341,15 @@ export default defineComponent({
                 </div>
               </Col>
               <Col span={6}>
+                <i class={styles.color12}></i>
+                <div class={styles.type}>
+                  <span>小组课</span>
+                  <span class={styles.price}>
+                    {moneyFormat(this.moneyInfo.groupAmount)}
+                  </span>
+                </div>
+              </Col>
+              <Col span={6}>
                 <i class={styles.color2}></i>
                 <div class={styles.type}>
                   <span>视频课</span>
@@ -464,6 +483,11 @@ export default defineComponent({
               <span>{this.moneyInfo.liveRate}%</span>
             </div>
             <div>
+              <i class={styles.pieGroup}></i>
+              <span class={styles.pieTitle}>小组课</span>
+              <span>{this.moneyInfo.groupRate}%</span>
+            </div>
+            <div>
               <i class={styles.pie2}></i>
               <span class={styles.pieTitle}>直播课推荐</span>
               <span>{this.moneyInfo.liveShareRate}%</span>

+ 27 - 0
src/teacher/share-page/share-group/index.module.less

@@ -0,0 +1,27 @@
+.live-detail {
+  .introduction {
+    color: #7a7a7a;
+    line-height: 23px;
+    padding-bottom: 8px;
+  }
+
+  :global {
+    .van-tabs__wrap {
+      margin-bottom: 15px;
+    }
+  }
+
+  .wxpopup {
+    width: 100%;
+    height: 100vh;
+    position: fixed;
+    top: 0;
+    left: 0;
+    background: rgba(0, 0, 0, 0.5);
+    z-index: 9999;
+    img {
+      width: 88%;
+      margin: 0 6%;
+    }
+  }
+}

+ 204 - 0
src/teacher/share-page/share-group/index.tsx

@@ -0,0 +1,204 @@
+import CoursePlanStep from '@/business-components/course-plan-step'
+import SectionDetail from '@/business-components/section-detail'
+import UserDetail from '@/business-components/user-detail'
+import ColSticky from '@/components/col-sticky'
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
+import request from '@/helpers/request'
+import { browser } from '@/helpers/utils'
+import { state } from '@/state'
+import dayjs from 'dayjs'
+import { Button, Dialog, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import { shareCall } from '../share'
+import styles from './index.module.less'
+import qs from 'query-string'
+export const getAssetsHomeFile = (fileName: string) => {
+  const path = `../images/${fileName}`
+  const modules = import.meta.globEager('../images/*')
+  return modules[path].default
+}
+interface IProps {
+  courseTime: string
+  coursePlan: string
+  videoPosterUrl?: string
+  roomUid?: string
+  liveState?: number
+  id?: number | string
+}
+export default defineComponent({
+  name: 'LiveDetail',
+  data() {
+    const query = this.$route.query
+    return {
+      recomUserId: query.recomUserId, // 分享人编号
+      groupId: query.groupId,
+      platform: query.p, // 来源
+      live: {} as any,
+      wxStatus: false
+    }
+  },
+  computed: {
+    userInfo() {
+      const live = this.live as any
+      const planList = live.planList || []
+      const startTime = planList[0]?.startTime || new Date()
+      const endTime = planList[0]?.endTime || new Date()
+      return {
+        headUrl: live.avatar,
+        username: live.userName,
+        isDegree: live.degreeFlag ? true : false,
+        isTeacher: live.teacherFlag ? true : false,
+        id: live.teacherId,
+        startTime:
+          `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(startTime).format(
+            'HH:mm'
+          )}~${dayjs(endTime).format('HH:mm')}` || '',
+        type: 'live',
+        lessonPrice: live.coursePrice,
+        buyNum: live.studentCount || 0,
+        lessonNum: live.courseNum || 0, // 课时数
+        lessonDesc: live.courseIntroduce,
+        lessonCoverUrl: live.backgroundPic || live.backgroundPicTemplate,
+        lessonName: live.courseGroupName,
+        auditVersion: 0
+      }
+    },
+    courseInfo() {
+      const tempArr = [] as IProps[]
+      const coursePlanList = this.live.planList || []
+      coursePlanList.forEach((item: any) => {
+        const startTime = item.startTime || new Date()
+        const endTime = item.endTime || new Date()
+        tempArr.push({
+          courseTime: `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(
+            startTime
+          ).format('HH:mm')}~${dayjs(endTime).format('HH:mm')}`,
+          coursePlan: item.plan,
+          roomUid: item.roomUid,
+          liveState: item.liveState,
+          id: item.courseId
+        })
+      })
+      return tempArr || []
+    }
+  },
+  created() {
+    if (browser().isApp) {
+      if (state.platformType === 'STUDENT') {
+        // 自动跳转到学生端视频课详情购买页
+        const query = this.$route.query
+        query.recomUserId =
+          query.userType && query.userType == 'STUDENT' ? '' : query.recomUserId
+        if (browser().ios) {
+          window.location.replace(
+            `${location.origin}/student/#/groupDetail??${qs.stringify(query)}`
+          )
+        } else {
+          postMessage({
+            api: 'openWebView',
+            content: {
+              url: `${location.origin}/student/#/groupDetail??${qs.stringify(
+                query
+              )}`,
+              orientation: 1,
+              isHideTitle: false
+            }
+          })
+
+          postMessage({ api: 'back' })
+        }
+      } else if (state.platformType === 'TEACHER') {
+        Dialog.alert({
+          title: '提示',
+          message: '请使用酷乐秀学生端扫码打开',
+          confirmButtonColor: '#2dc7aa'
+        }).then(() => {
+          postMessage({ api: 'back' })
+        })
+      }
+    } else {
+      // 如果不在app里面则不需要唤起操作
+      this.reCall()
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.post('/api-teacher/open/liveShareProfit', {
+        data: {
+          bizId: this.groupId,
+          userId: this.recomUserId
+        }
+      })
+      this.live = res.data.liveCourseGroup || {}
+    } catch {
+      //
+    }
+  },
+  methods: {
+    locationReplace(url: string) {
+      if (history.replaceState) {
+        history.replaceState(null, document.title, url)
+        window.location.reload()
+      } else {
+        location.replace(url)
+      }
+    },
+    reCall() {
+      const { origin } = location
+      let str = origin + '/student/#/groupDetail'
+      const params = this.$route.query
+      str += `?recomUserId=${
+        params.userType && params.userType == 'STUDENT' ? '' : this.recomUserId
+      }&groupId=${params.groupId}&p=${params.p}`
+      shareCall(str, {})
+    },
+    onShare() {
+      if (browser().weixin) {
+        this.wxStatus = true
+        return
+      }
+      this.reCall()
+      setTimeout(() => {
+        window.location.href = location.origin + '/student/#/download'
+      }, 3000)
+    }
+  },
+  render() {
+    return (
+      <div class={[styles['live-detail'], 'mb12']}>
+        <UserDetail userInfo={this.userInfo} showBuy={false} />
+        <SectionDetail border>
+          <p class={styles.introduction}>{this.userInfo.lessonDesc}</p>
+        </SectionDetail>
+
+        <SectionDetail
+          title="课程列表"
+          icon="courseList"
+          border
+          // contentStyle={{ paddingTop: '0' }}
+        >
+          <CoursePlanStep courseInfo={this.courseInfo} hideVideo={true} />
+        </SectionDetail>
+
+        <ColSticky position="bottom">
+          <div class={['btnGroup']} style={{ paddingTop: '12px' }}>
+            <Button block round type="primary" onClick={this.onShare}>
+              下载酷乐秀进入课程
+            </Button>
+          </div>
+        </ColSticky>
+
+        {this.wxStatus && (
+          <div
+            class={styles.wxpopup}
+            onClick={() => {
+              this.wxStatus = false
+            }}
+          >
+            <img src={getAssetsHomeFile('wx_bg.png')} alt="" />
+          </div>
+        )}
+      </div>
+    )
+  }
+})

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

@@ -20,7 +20,7 @@ export const formatOrderDetail = async (item: any, amount?: IAmount) => {
   let tempList: any = {}
 
   switch (type) {
-    case 'LIVE':
+    case 'LIVE': case "GROUP":
       {
         try {
           const live = await getLiveDetail(item.bizId)

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

@@ -43,6 +43,8 @@ import OrderTennatAlbum from './order-tennat-album'
 import OrderDiscount from './order-discount'
 import OrderVipCourse from './order-vip-course'
 import AddDiscount from './add-discount'
+import { orderType } from '@/constant';
+import OrderGroup from './order-group'
 
 /** 保留两位小数向上取整 */
 export const numberToTwoUp = (num: number | string) => {
@@ -239,11 +241,12 @@ export default defineComponent({
         }
         if (
           orderObject.orderType === 'LIVE' ||
+          orderObject.orderType === "GROUP" ||
           orderObject.orderType === 'VIDEO'
         ) {
           const orderItem = orderObject.orderList.find(
             (item: any) =>
-              item.orderType === 'VIDEO' || item.orderType === 'LIVE'
+              item.orderType === 'VIDEO' || item.orderType === 'LIVE' || item.orderType === "GROUP"
           )
           bizId = orderItem ? orderItem.courseGroupId : ''
         }
@@ -669,6 +672,8 @@ export default defineComponent({
             {this.orderList.map((item: any) => {
               if (item.orderType === 'VIDEO') {
                 return <OrderVideo item={item} />
+              } else if(item.orderType === "GROUP") {
+                return <OrderGroup item={item} />
               } else if (item.orderType === 'LIVE') {
                 return <OrderLive item={item} />
               } else if (item.orderType === 'PRACTICE') {

+ 74 - 0
src/views/order-detail/order-group/index.module.less

@@ -0,0 +1,74 @@
+.liveOrder {
+  .tag {
+    margin-right: 5px;
+    padding: 2px 5px;
+    font-size: 11px;
+    font-weight: 500;
+    color: #7935FF;
+    line-height: 16px;
+    background: #F0E6FF;
+    border-radius: 4px;
+    vertical-align: middle;
+  }
+  .title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+  }
+
+  .courseGroupName {
+    max-width: 250px;
+    display: inline-block;
+    vertical-align: middle;
+  }
+  .collapseItem {
+    :global(.van-cell) {
+      background: #f7f8f9;
+      padding-top: 9px;
+      padding-bottom: 9px;
+    }
+  }
+
+  .teacher {
+    margin-left: 5px;
+    color: #1a1a1a;
+    font-size: 16px;
+    font-weight: 500;
+  }
+
+  .price {
+    font-weight: 600;
+    font-size: 14px;
+    color: #131415;
+    line-height: 20px;
+    i {
+      padding-right: 2px;
+      font-style: normal;
+      font-size: 14px;
+    }
+  }
+  .userLogo {
+    width: 28px;
+    height: 28px;
+    overflow: hidden;
+    border-radius: 50%;
+  }
+  .classItem {
+    font-size: 14px;
+    color: #333333;
+    line-height: 20px;
+    .time {
+      padding-bottom: 6px;
+    }
+    p {
+      color: var(--van-primary);
+    }
+  }
+  :global {
+    .van-cell-group {
+      margin-bottom: 10px;
+      border-radius: 8px;
+      overflow: hidden;
+    }
+  }
+}

+ 77 - 0
src/views/order-detail/order-group/index.tsx

@@ -0,0 +1,77 @@
+import { Cell, CellGroup, Collapse, CollapseItem, Image } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+
+import iconTeacher from '@common/images/icon_teacher.png'
+import { moneyFormat } from '@/helpers/utils'
+
+export default defineComponent({
+  name: 'OrderLive',
+  props: {
+    item: {
+      type: Object,
+      default: {}
+    }
+  },
+  data() {
+    return {
+      collapse: [1]
+    }
+  },
+  render() {
+    const item = this.item
+    return (
+      <div class={styles.liveOrder}>
+        <CellGroup border={false}>
+          <Cell
+            center
+            v-slots={{
+              title: () => (
+                <div class={[styles.title, 'van-ellipsis']}>
+                  <span class={styles.tag}>小组课</span>
+                  <span class={[styles.courseGroupName, 'van-ellipsis']}>{item.courseGroupName}</span>
+                </div>
+              )
+            }}
+          />
+          <Cell
+            center
+            border={false}
+            title={item.teacherName}
+            titleClass={styles.teacher}
+            v-slots={{
+              icon: () => (
+                <Image
+                  class={styles.userLogo}
+                  src={item.avatar || iconTeacher}
+                />
+              ),
+              default: () => (
+                <span class={styles.price}>
+                  <i>¥</i> 
+                  {moneyFormat(item.coursePrice)}
+                </span>
+              )
+            }}
+          />
+          <Cell border={false}>
+            <Collapse border={false} v-model={this.collapse}>
+              <CollapseItem
+                title="课程详情"
+                name={1}
+                class={styles.collapseItem}
+              >
+                <div class={styles.classItem}>
+                  {item.courseInfo.map((item: any) => (
+                    <div class={styles.time}>{item.courseTime}</div>
+                  ))}
+                </div>
+              </CollapseItem>
+            </Collapse>
+          </Cell>
+        </CellGroup>
+      </div>
+      // 视频课
+    )
+  }
+})

+ 3 - 2
src/views/order-detail/orderStatus.ts

@@ -8,6 +8,7 @@ import dayjs from 'dayjs'
 type orderType =
   | 'VIDEO'
   | 'LIVE'
+  | 'GROUP'
   | 'PRACTICE'
   | 'GOODS'
   | 'VIP'
@@ -111,7 +112,7 @@ export const orderInfos = () => {
         videoLessonGroupId: item.courseGroupId,
         payMoney: item.coursePrice || 0
       }
-    } else if (item.orderType === 'LIVE') {
+    } else if (item.orderType === 'LIVE' || item.orderType === 'GROUP') {
       params.bizContent = {
         groupId: item.courseGroupId
       }
@@ -237,7 +238,7 @@ export const orderTenantInfos = (otherGoods: any[] = []) => {
       params.bizId = item.id
       params.buyNumber = 1
       params.buyMultiple = 1
-    } else if (item.orderType === 'LIVE') {
+    } else if (item.orderType === 'LIVE' || item.orderType === 'GROUP') {
       params.bizContent = {
         groupId: item.courseGroupId
       }

+ 2 - 0
src/views/order-detail/use-coupons/index.tsx

@@ -19,6 +19,7 @@ export const couponEnum = {
   DISCOUNT: 'DISCOUNT',
   PRACTICE: 'SPARRING',
   LIVE: 'LIVE',
+  GROUP: 'GROUP',
   VIDEO: 'VIDEO',
   ALBUM: 'ALBUM'
 }
@@ -38,6 +39,7 @@ export const couponToOrderTypeEnum = {
   DISCOUNT: 'DISCOUNT',
   SPARRING: 'PRACTICE',
   LIVE: 'LIVE',
+  GROUP: 'GROUP',
   VIDEO: 'VIDEO',
   ALBUM: 'ALBUM'
 }