lex-xin 2 年之前
父節點
當前提交
cef5a347b2

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

@@ -121,7 +121,7 @@ export default [
       },
       {
         path: '/music-album',
-        component: () => import('@/student/music/album'),
+        component: () => import('@/student/music/album/index'),
         meta: {
           title: '专辑'
         }
@@ -160,6 +160,13 @@ export default [
         meta: {
           title: '上传曲谱'
         }
+      },
+      {
+        path: '/teacherFollow',
+        component: () => import('@/student/teacher-dependent/teacher-follow'),
+        meta: {
+          title: '我的关注'
+        }
       }
     ]
   },

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

@@ -109,6 +109,14 @@ export default [
         meta: {
           title: '陪练课设置'
         }
+      },
+      {
+        path: '/myFans',
+        name: 'myFans',
+        component: () => import('@/teacher/my-fans/index'),
+        meta: {
+          title: '我的粉丝'
+        }
       }
     ]
   },

+ 1 - 0
src/student/member-center/index.module.less

@@ -51,6 +51,7 @@
       height: 46px;
       border-radius: 50%;
       vertical-align: middle;
+      overflow: hidden;
     }
     .userInfo {
       display: flex;

+ 2 - 2
src/student/music/album/index.tsx

@@ -24,7 +24,7 @@ export default defineComponent({
     const route = useRoute()
     const params = reactive({
       search: (route.query.search as string) || '',
-      musicTagIds: route.query.tagids || '',
+      albumTagIds: route.query.tagids || '',
       page: 1,
       ...defauleParams
     })
@@ -64,7 +64,7 @@ export default defineComponent({
 
     const onComfirm = tags => {
       const data = Object.values(tags).flat().filter(Boolean).join(',')
-      params.musicTagIds = data
+      params.albumTagIds = data
       params.page = 1
       FetchList()
       tagVisibility.value = false

+ 64 - 14
src/student/practice-class/index.tsx

@@ -1,7 +1,17 @@
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import ColHeader from '@/components/col-header'
-import { Button, Icon, Popup, RadioGroup, Sticky, Radio, Tag, List } from 'vant'
+import {
+  Button,
+  Icon,
+  Popup,
+  RadioGroup,
+  Sticky,
+  Radio,
+  Tag,
+  List,
+  ActionSheet
+} from 'vant'
 import ColSearch from '@/components/col-search'
 import PracticeItem from './practice-item'
 import request from '@/helpers/request'
@@ -10,6 +20,35 @@ import AllSearch from './model/all-search'
 import OrganSearch from './model/organ-search'
 import { state } from '@/state'
 
+// {"subjectId":null,"search":"","sort":"starGrade ASC,expTime DESC,subjectPrice DESC"}
+const actions = [
+  {
+    name: '不限制',
+    value: '',
+    color: 'var(--van-primary)'
+  },
+  {
+    name: '单价最高',
+    value: 'subjectPrice DESC',
+    color: '#333'
+  },
+  {
+    name: '单价最低',
+    value: 'subjectPrice ASC',
+    color: '#333'
+  },
+  {
+    name: '课时数最多',
+    value: 'expTime DESC',
+    color: '#333'
+  },
+  {
+    name: '评分最高',
+    value: 'starGrade DESC',
+    color: '#333'
+  }
+]
+
 export default defineComponent({
   name: 'practiceClass',
   data() {
@@ -35,7 +74,8 @@ export default defineComponent({
         subjectId: null as any,
         page: 1,
         rows: 20
-      }
+      },
+      show: false
     }
   },
   async mounted() {
@@ -52,15 +92,7 @@ export default defineComponent({
       this.params.search = _search
       this.onSort()
     },
-    onSort(type?: any) {
-      const popupParams = type || this.tempSort
-      let str: any = []
-      for (let i in popupParams) {
-        if (popupParams[i] !== 'ALL') {
-          str.push(`${i} ${popupParams[i]}`)
-        }
-      }
-      this.params.sort = str.join(',')
+    onSort() {
       this.params.page = 1
       this.list = []
       this.dataShow = true // 判断是否有数据
@@ -69,6 +101,17 @@ export default defineComponent({
       this.searchStatus = false
       this.getList()
     },
+    onSheetSelect(item: any) {
+      actions.forEach((v: any) => {
+        v.color = '#333'
+        if (v.value === item.value) {
+          v.color = 'var(--van-primary)'
+        }
+      })
+      this.params.sort = item.value
+      this.show = false
+      this.onSort()
+    },
     async getList() {
       try {
         if (this.dataLoading) {
@@ -149,8 +192,7 @@ export default defineComponent({
               <div
                 class={styles.dataItem}
                 onClick={() => {
-                  this.searchStatus = !this.searchStatus
-                  this.searchType = 'all'
+                  this.show = true
                 }}
               >
                 筛选
@@ -199,6 +241,14 @@ export default defineComponent({
           />
         )}
 
+        <ActionSheet
+          show={this.show}
+          actions={actions}
+          cancelText="取消"
+          onSelect={this.onSheetSelect}
+          onCancel={() => (this.show = false)}
+        />
+
         <Popup
           show={this.searchStatus}
           position="bottom"
@@ -208,7 +258,7 @@ export default defineComponent({
           onClose={() => (this.searchStatus = false)}
           onClosed={() => (this.openStatus = false)}
         >
-          {this.searchType === 'all' && <AllSearch onSort={this.onSort} />}
+          {/* {this.searchType === 'all' && <AllSearch onSort={this.onSort} />} */}
           {this.searchType === 'organ' && this.openStatus && (
             <OrganSearch
               subjectList={this.subjectList}

+ 26 - 2
src/student/teacher-dependent/components/music.tsx

@@ -1,8 +1,10 @@
 import ColResult from '@/components/col-result'
-import { List } from 'vant'
+import { Dialog, List } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './music.module.less'
 import MusicList from '@/student/music/list'
+import { state } from '@/state'
+import { orderStatus } from '@/views/order-detail/orderStatus'
 
 export default defineComponent({
   name: 'music',
@@ -20,7 +22,29 @@ export default defineComponent({
   },
   methods: {
     onItemClick(item: any) {
-      console.log(item)
+      // const memberRankSettingId = state.user.data.memberRankSettingId
+
+      // play :0 不能看,1 能看
+      if (!item.play && item.chargeType === 'VIP') {
+        Dialog.confirm({
+          title: '提示',
+          message: '您还不是会员,是否加入会员?',
+          confirmButtonColor: 'var(--van-primary)'
+        }).then(() => {
+          this.$router.push('/memberCenter')
+        })
+        return
+      } else if (!item.play && item.chargeType === 'CHARGE') {
+        orderStatus.orderType = 'MUSIC'
+        orderStatus.musicInfo = item
+        this.$router.push({
+          path: '/orderDetail',
+          query: {
+            orderType: 'MUSIC'
+          }
+        })
+      }
+      // 去播放页面
     }
   },
   render() {

+ 14 - 2
src/student/teacher-dependent/teacher-elegant.tsx

@@ -30,8 +30,11 @@ export default defineComponent({
     }
   },
   async mounted() {
+    const sessionSubjectId = sessionStorage.getItem('elegantSubjectId')
     sessionStorage.removeItem('teacherHomeTabs')
-    this.params.subjectId = state.user.data?.subjectId || null
+    sessionStorage.removeItem('elegantSubjectId')
+    this.params.subjectId =
+      sessionSubjectId || state.user.data?.subjectId || null
     this.params.subjectName = state.user.data?.subjectName || ''
     try {
       const res = await request.get('/api-student/subject/subjectSelect')
@@ -40,6 +43,12 @@ export default defineComponent({
 
     await this.getList()
   },
+  computed: {
+    filterDot() {
+      const subjectId: any = this.params.subjectId
+      return !!subjectId
+    }
+  },
   methods: {
     async getList() {
       try {
@@ -75,6 +84,8 @@ export default defineComponent({
       this.finished = false
       this.searchStatus = false
       this.getList()
+
+      sessionStorage.setItem('elegantSubjectId', this.params.subjectId)
     },
     onSearch(_search?: any) {
       this.params.username = _search
@@ -92,7 +103,7 @@ export default defineComponent({
   render() {
     return (
       <div class={styles['teacher-elegant']}>
-        <Sticky offsetTop={0}>
+        <Sticky offsetTop={0} position="top">
           <ColHeader
             class={styles.classHeader}
             border={false}
@@ -102,6 +113,7 @@ export default defineComponent({
           <ColSearch
             placeholder="请输入老师名称"
             showAction
+            filterDot={this.filterDot}
             onSearch={this.onSearch}
             onFilter={() => {
               this.searchStatus = !this.searchStatus

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


+ 118 - 0
src/student/teacher-dependent/teacher-follow.tsx

@@ -0,0 +1,118 @@
+import { Cell, Icon, Image, Rate } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './teacher-home.module.less'
+import iconTeacher from '@common/images/icon_teacher.png'
+import musicCert from '@common/images/music_cert.png'
+import teacherCert from '@common/images/teacher_cert.png'
+export const getAssetsHomeFile = (fileName: string) => {
+  const path = `./images/${fileName}`
+  const modules = import.meta.globEager('./images/*')
+  return modules[path].default
+}
+
+export default defineComponent({
+  name: 'teacher-follow',
+  data() {
+    return {
+      userInfo: {} as any,
+      starGrade: 0,
+      subjectNameList: []
+    }
+  },
+  methods: {
+    onUnLike() {}
+  },
+  render() {
+    return (
+      <div class={styles.teacherFollow}>
+        <div class={[styles.headerCount, styles.headerFollow]}>
+          <Cell
+            class={styles['open-teacher-info']}
+            border={false}
+            center
+            v-slots={{
+              icon: () => (
+                <Image
+                  class={styles.userLogo}
+                  src={this.userInfo.heardUrl || iconTeacher}
+                  fit="cover"
+                />
+              ),
+              'right-icon': () => (
+                <Icon
+                  name="like"
+                  color="#FF6363"
+                  size={18}
+                  onClick={this.onUnLike}
+                />
+              )
+            }}
+          >
+            <div class={styles['teacher-info']}>
+              <div class={styles['teacher-name']}>
+                <div class={styles.teacherCert}>
+                  <span style={{ display: 'inline-block' }}>
+                    {this.userInfo.username ||
+                      `游客${this.userInfo.userId || ''}`}
+                  </span>
+
+                  {this.userInfo.entryFlag === 1 && (
+                    <Image
+                      class={styles.cert}
+                      src={teacherCert}
+                      // fit="contain"
+                    />
+                  )}
+                  {this.userInfo.musicianFlag === 1 && (
+                    <Image
+                      class={styles.cert}
+                      src={musicCert}
+                      // fit="contain"
+                    />
+                  )}
+                </div>
+              </div>
+              <div class={styles.level}>
+                {this.starGrade ? (
+                  <Rate
+                    readonly
+                    modelValue={this.starGrade}
+                    iconPrefix="iconfont"
+                    color="#FFC459"
+                    void-icon="star_default"
+                    icon="star_active"
+                    size={15}
+                  />
+                ) : (
+                  <span style={{ fontSize: '12px', color: '#999999' }}>
+                    暂无评分
+                  </span>
+                )}
+              </div>
+            </div>
+          </Cell>
+          <p class={styles.piNameSubject}>
+            <Image
+              class={styles.subjectSection}
+              src={getAssetsHomeFile('icon_subject.png')}
+              fit="contain"
+            />
+            {this.subjectNameList.map((item: any) => (
+              <span class={styles.subject}>{item}</span>
+            ))}
+          </p>
+          <div class={styles['teacher-bottom']}>
+            <div class={styles['teacher-data']}>
+              <div class={styles['teacher-data_item']}>
+                粉丝 <span>{this.userInfo.fansNum || 0}</span>
+              </div>
+              <div class={styles['teacher-data_item']}>
+                已上课时 <span>{this.userInfo.expTime || 0}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 14 - 0
src/student/teacher-dependent/teacher-home.module.less

@@ -40,6 +40,10 @@
   position: relative;
 }
 
+.teacherFollow {
+  background: #f7f8f9;
+  overflow: hidden;
+}
 .headerCount {
   position: absolute;
   bottom: -100px;
@@ -50,6 +54,12 @@
   overflow: hidden;
   height: 161px;
 }
+
+.headerFollow {
+  position: relative;
+  bottom: 0;
+}
+
 .open-teacher-info {
   width: auto;
   background-color: transparent !important;
@@ -67,6 +77,10 @@
       // justify-content: space-between;
     }
   }
+
+  .level {
+    line-height: 1;
+  }
 }
 
 .subjectSection {

+ 12 - 3
src/student/trade/index.tsx

@@ -2,25 +2,34 @@ import { Cell, Sticky, Tab, Tabs } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import List from './list'
+import { useElementSize } from '@vueuse/core'
 
 export default defineComponent({
   name: 'tradeRecord',
   data() {
     return {
-      active: 'buy'
+      active: 'buy',
+      height: 44
     }
   },
+  mounted() {
+    this.$nextTick(() => {
+      const { width, height } = useElementSize((this as any).$refs.tabs)
+      console.log(width.value, height.value, width, height)
+    })
+  },
   render() {
     return (
       <div class={styles.tradeRecord}>
         <Tabs
           v-model:active={this.active}
           color="var(--van-primary)"
-          lineWidth={28}
+          ref="tabs"
           sticky
+          lineWidth={28}
         >
           <Tab name="buy" title="购买记录">
-            <List />
+            <List height={this.height} />
           </Tab>
           <Tab name="refund" title="退费记录">
             {/* <List type="refund" /> */}

+ 134 - 3
src/student/trade/list/index.tsx

@@ -25,6 +25,10 @@ export default defineComponent({
     type: {
       type: String as PropType<'buy' | 'refund'>,
       default: 'buy'
+    },
+    height: {
+      type: Number,
+      default: 44
     }
   },
   data() {
@@ -109,7 +113,7 @@ export default defineComponent({
   render() {
     return (
       <div class={styles.tradeList}>
-        <Sticky offsetTop={44} position="top">
+        <Sticky position="top" offsetTop={44}>
           <Cell
             center
             style={{ backgroundColor: '#F7F8F9' }}
@@ -130,7 +134,7 @@ export default defineComponent({
                   <Icon
                     classPrefix="iconfont"
                     name="down"
-                    size={8}
+                    size={12}
                     color="var(--van-primary)"
                   />
                 </div>
@@ -146,7 +150,7 @@ export default defineComponent({
                   <Icon
                     classPrefix="iconfont"
                     name="down"
-                    size={8}
+                    size={12}
                     color="var(--van-primary)"
                   />
                 </div>
@@ -164,6 +168,133 @@ export default defineComponent({
           >
             {this.list.map((item: any) => (
               <CellGroup
+                border={false}
+                onClick={() => {
+                  this.onDetail(item)
+                }}
+              >
+                <Cell
+                  title={dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
+                  value={orderType[item.status]}
+                  valueClass={styles.tradeType}
+                />
+                <Cell
+                  v-slots={{
+                    // icon: () => (
+                    //   <Image
+                    //     class={styles.tradeLogo}
+                    //     src={iconTeacher}
+                    //     fit="cover"
+                    //   />
+                    // ),
+                    title: () => (
+                      <div class={styles.title}>
+                        <span>{item.orderName}</span>
+                        <span class={styles.desc}>
+                          {goodsType[item.orderType]}
+                        </span>
+                      </div>
+                    ),
+                    default: () => (
+                      <div class={styles.content}>
+                        <span class={styles.price}>
+                          ¥
+                          {(this as any).$filters.moneyFormat(item.actualPrice)}
+                        </span>
+                        {/* <span class={styles.num}>x1</span> */}
+                      </div>
+                    )
+                  }}
+                />
+              </CellGroup>
+            ))}
+            {this.list.map((item: any) => (
+              <CellGroup
+                border={false}
+                onClick={() => {
+                  this.onDetail(item)
+                }}
+              >
+                <Cell
+                  title={dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
+                  value={orderType[item.status]}
+                  valueClass={styles.tradeType}
+                />
+                <Cell
+                  v-slots={{
+                    // icon: () => (
+                    //   <Image
+                    //     class={styles.tradeLogo}
+                    //     src={iconTeacher}
+                    //     fit="cover"
+                    //   />
+                    // ),
+                    title: () => (
+                      <div class={styles.title}>
+                        <span>{item.orderName}</span>
+                        <span class={styles.desc}>
+                          {goodsType[item.orderType]}
+                        </span>
+                      </div>
+                    ),
+                    default: () => (
+                      <div class={styles.content}>
+                        <span class={styles.price}>
+                          ¥
+                          {(this as any).$filters.moneyFormat(item.actualPrice)}
+                        </span>
+                        {/* <span class={styles.num}>x1</span> */}
+                      </div>
+                    )
+                  }}
+                />
+              </CellGroup>
+            ))}
+            {this.list.map((item: any) => (
+              <CellGroup
+                border={false}
+                onClick={() => {
+                  this.onDetail(item)
+                }}
+              >
+                <Cell
+                  title={dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
+                  value={orderType[item.status]}
+                  valueClass={styles.tradeType}
+                />
+                <Cell
+                  v-slots={{
+                    // icon: () => (
+                    //   <Image
+                    //     class={styles.tradeLogo}
+                    //     src={iconTeacher}
+                    //     fit="cover"
+                    //   />
+                    // ),
+                    title: () => (
+                      <div class={styles.title}>
+                        <span>{item.orderName}</span>
+                        <span class={styles.desc}>
+                          {goodsType[item.orderType]}
+                        </span>
+                      </div>
+                    ),
+                    default: () => (
+                      <div class={styles.content}>
+                        <span class={styles.price}>
+                          ¥
+                          {(this as any).$filters.moneyFormat(item.actualPrice)}
+                        </span>
+                        {/* <span class={styles.num}>x1</span> */}
+                      </div>
+                    )
+                  }}
+                />
+              </CellGroup>
+            ))}
+            {this.list.map((item: any) => (
+              <CellGroup
+                border={false}
                 onClick={() => {
                   this.onDetail(item)
                 }}

+ 36 - 0
src/teacher/my-fans/index.module.less

@@ -0,0 +1,36 @@
+.piNameSubject {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+
+  .subject {
+    margin-left: 5px;
+    background: #fff1de;
+    border-radius: 4px;
+    font-size: 12px;
+    color: #ff8c00;
+    line-height: 16px;
+    padding: 0px 4px;
+    &:first-child {
+      margin-left: 0;
+    }
+  }
+}
+
+.myFans {
+  margin: 12px 14px 0;
+  width: auto;
+}
+
+.userName {
+  font-size: 16px;
+  font-weight: 500;
+  color: #1a1a1a;
+  line-height: 28px;
+}
+
+.userImg {
+  width: 46px;
+  height: 46px;
+  border-radius: 23px;
+}

+ 92 - 0
src/teacher/my-fans/index.tsx

@@ -0,0 +1,92 @@
+import { Cell, Image, List } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import iconStudent from '@common/images/icon_student.png'
+import request from '@/helpers/request'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'my-fans',
+  data() {
+    return {
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      params: {
+        page: 1,
+        rows: 20
+      }
+    }
+  },
+  mounted() {},
+  methods: {
+    async getList() {
+      try {
+        let params = this.params
+        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
+      }
+    }
+  },
+  render() {
+    return (
+      <div style={{ overflow: 'hidden' }}>
+        {this.dataShow ? (
+          <List
+            v-model:loading={this.loading}
+            finished={this.finished}
+            finishedText=" "
+            class={[styles.liveList, 'mb12']}
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <Cell
+                class={styles.myFans}
+                titleStyle={{ paddingLeft: '8px' }}
+                v-slots={{
+                  icon: () => (
+                    <Image
+                      class={styles.userImg}
+                      src={iconStudent}
+                      fit="cover"
+                    />
+                  ),
+                  title: () => (
+                    <div class={styles.userInfo}>
+                      <div class={styles.userName}> 学生 </div>
+                      <div class={styles.piNameSubject}>
+                        <span class={styles.subject}>长笛</span>
+                      </div>
+                    </div>
+                  )
+                }}
+              />
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无粉丝" />
+        )}
+      </div>
+    )
+  }
+})

+ 3 - 1
src/views/article-center/help-center-detail.tsx

@@ -21,12 +21,14 @@ export default defineComponent({
       const res = await request.get(
         '/api-cms/helpCenterContent/get/' + query.id
       )
-      let { title, createTime, content } = res.data
+      let { title, createTime, content, catalogId } = res.data
       this.detail = {
         title,
         createTime: dayjs(createTime).format('YYYY-MM-DD HH:mm:ss'),
         content
       }
+
+      catalogId == 2 && (document.title = '公告详情')
     } catch {}
   },
   methods: {

+ 3 - 1
src/views/article-center/help-center.tsx

@@ -26,7 +26,9 @@ export default defineComponent({
       }
     }
   },
-  async mounted() {},
+  async mounted() {
+    this.params.catalogIds == 2 && (document.title = '公告列表')
+  },
   methods: {
     async getList() {
       try {

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

@@ -22,6 +22,7 @@ import OrderVideo from './order-video'
 import OrderLive from './order-live'
 import OrderPractice from './order-practice'
 import OrderVip from './order-vip'
+import OrderMusic from './order-music'
 
 export default defineComponent({
   name: 'order-detail',
@@ -74,7 +75,8 @@ export default defineComponent({
           result = await (this as any).$refs.orderPractice.onSubmit()
         } else if (this.orderType === 'VIP') {
           result = await (this as any).$refs.orderVip.onSubmit()
-          console.log(result)
+        } else if (this.orderType === 'MUSIC') {
+          result = await (this as any).$refs.orderMusic.onSubmit()
         }
         if (result) {
           orderStatus.orderInfo = {
@@ -103,6 +105,9 @@ export default defineComponent({
         {this.orderType === 'VIP' && (
           <OrderVip ref="orderVip" v-model={this.orderPrice} />
         )}
+        {this.orderType === 'MUSIC' && (
+          <OrderMusic ref="orderMusic" v-model={this.orderPrice} />
+        )}
 
         <div class={styles.tips}>
           <h3>

+ 81 - 0
src/views/order-detail/order-music/index.module.less

@@ -0,0 +1,81 @@
+.item {
+  background-color: var(--music-list-item-background-color);
+  // margin: 10px 14px;
+  margin-bottom: 12px;
+  padding: 10px;
+  border-radius: 9px;
+  .header {
+    display: flex;
+    align-items: center;
+    border-bottom: 1px solid var(--music-list-item-border-color);
+    padding-bottom: 12px;
+    .mate {
+      display: flex;
+      flex: 1;
+      align-items: center;
+      .icon {
+        width: 40px;
+        height: 40px;
+      }
+      .info {
+        margin-left: 14px;
+        > h4 {
+          color: var(--music-list-item-title-color);
+          font-size: 14px;
+          font-weight: 600;
+        }
+        > p {
+          color: var(--music-list-item-mate-color);
+          line-height: 17px;
+        }
+      }
+    }
+    .btn {
+      width: 54px;
+      height: 22px;
+      font-size: 12px;
+      border-radius: 11px;
+      padding: 0;
+      border: none;
+      &.vip {
+        background-color: var(--music-list-item-vip-bg);
+        color: var(--music-list-item-vip-color);
+      }
+      &.free {
+        background-color: var(--music-list-item-free-bg);
+        color: var(--music-list-item-free-color);
+      }
+      &.charge {
+        background-color: var(--music-list-item-charge-bg);
+        color: var(--music-list-item-charge-color);
+      }
+    }
+  }
+  .footer {
+    display: flex;
+    padding-top: 8px;
+    align-items: center;
+    justify-content: space-between;
+    .user {
+      display: flex;
+      align-items: center;
+      padding: 0 10px;
+      .userIcon {
+        width: 20px;
+        height: 20px;
+        margin-right: 8px;
+      }
+    }
+    .favorite {
+      font-size: 16px;
+    }
+    .tags {
+      display: flex;
+      align-items: center;
+      --van-tag-default-color: #fff1de;
+      --van-tag-text-color: #ff8c00;
+    }
+  }
+
+  --van-button-disabled-opacity: 1;
+}

+ 113 - 0
src/views/order-detail/order-music/index.tsx

@@ -0,0 +1,113 @@
+import { Button, Icon, Image, Tag } from 'vant'
+import classNames from 'classnames'
+import MusicIcon from '@/student/music/list/icons/music-icon.png'
+import InitUserIcon from '@/student/music/list/icons/init-user-icon.png'
+import FavoriteIcon from '@/student/music/album/favorite.svg'
+import FavoritedIcon from '@/student/music/album/favorited.svg'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import { orderStatus } from '../orderStatus'
+
+import request from '@/helpers/request'
+
+const chargeTypes = {
+  CHARGE: '点播',
+  FREE: '免费',
+  VIP: 'VIP'
+}
+
+export default defineComponent({
+  name: 'OrderVideo',
+  props: {
+    modelValue: {
+      type: Number,
+      default: 0
+    }
+  },
+  mounted() {
+    const price = orderStatus.musicInfo.musicPrice || 0
+    this.$emit('update:modelValue', price)
+  },
+  computed: {
+    data() {
+      return orderStatus.musicInfo
+    }
+  },
+  methods: {
+    async onSubmit() {
+      try {
+        const res = await request.post('/api-student/userOrder/executeOrder', {
+          data: {
+            orderName: orderStatus.musicInfo.musicSheetName,
+            orderDesc: orderStatus.musicInfo.musicSheetName,
+            orderType: 'MUSIC',
+            actualPrice: orderStatus.musicInfo.musicPrice || 0,
+            orderInfos: [
+              {
+                goodType: 'MUSIC',
+                goodName: orderStatus.musicInfo.musicSheetName,
+                bizContent: {
+                  musicSheetId: orderStatus.musicInfo.id,
+                  actualPrice: orderStatus.musicInfo.musicPrice || 0
+                }
+              }
+            ]
+          }
+        })
+        return res.data
+      } catch {
+        return false
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.item}>
+        <header class={styles.header}>
+          <div class={styles.mate}>
+            <Image src={MusicIcon} round class={styles.icon} />
+            <div class={styles.info}>
+              <h4>{this.data.musicSheetName}</h4>
+              <p>{this.data.composer}</p>
+            </div>
+          </div>
+          <div class={styles.buttons}>
+            <Button
+              class={classNames(
+                styles.btn,
+                styles[this.data.chargeType.toLocaleLowerCase()]
+              )}
+              disabled
+            >
+              {chargeTypes[this.data.chargeType]}
+              <Icon name="arrow" />
+            </Button>
+          </div>
+        </header>
+        <footer class={styles.footer}>
+          <div class={styles.user}>
+            <Image
+              round
+              src={this.data.addUserAvatar || InitUserIcon}
+              class={styles.userIcon}
+            />
+            <p>{this.data.addName}</p>
+            <div class={styles.tags}>
+              {(this.data.subjectNames || '').split(',').map(item => (
+                <Tag>{item}</Tag>
+              ))}
+            </div>
+          </div>
+          <div class={styles.icons}>
+            <Button style={{ border: 'none' }} disabled>
+              <Icon
+                class={styles.favorite}
+                name={this.data.value ? FavoritedIcon : FavoriteIcon}
+              />
+            </Button>
+          </div>
+        </footer>
+      </div>
+    )
+  }
+})

+ 16 - 0
src/views/order-detail/order-vip/index.module.less

@@ -25,3 +25,19 @@
     font-size: 14px;
   }
 }
+
+.timerTitle {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 20px;
+  padding-bottom: 8px;
+}
+
+.timer {
+  font-size: 13px;
+  color: #999999;
+  line-height: 18px;
+}

+ 29 - 4
src/views/order-detail/order-vip/index.tsx

@@ -1,9 +1,10 @@
-import { Cell, CellGroup, Image } from 'vant'
+import { Cell, CellGroup, Icon, Image } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import { orderStatus } from '../orderStatus'
 
 import iconMember from '@common/images/icon_member.png'
+import iconTimer from '@common/images/icon_timer.png'
 import request from '@/helpers/request'
 
 export default defineComponent({
@@ -25,14 +26,14 @@ export default defineComponent({
       try {
         const res = await request.post('/api-student/userOrder/executeOrder', {
           data: {
-            orderName: '会员购买',
-            orderDesc: '会员购买',
+            orderName: '云教练' + orderStatus.vipInfo.title,
+            orderDesc: '云教练' + orderStatus.vipInfo.title,
             orderType: 'VIP',
             actualPrice: orderStatus.vipInfo.price || 0,
             orderInfos: [
               {
                 goodType: 'VIP',
-                goodName: '会员购买',
+                goodName: '云教练' + orderStatus.vipInfo.title,
                 bizContent: orderStatus.vipInfo.id
               }
             ]
@@ -73,6 +74,30 @@ export default defineComponent({
             }}
           />
         </CellGroup>
+
+        <CellGroup
+          class={'mb12'}
+          border={false}
+          style={{ borderRadius: '8px' }}
+        >
+          <Cell
+            center
+            v-slots={{
+              title: () => (
+                <div class={styles.container}>
+                  <div class={styles.timerTitle}>
+                    <Icon name={iconTimer} size={18} />
+                    <span style={{ paddingLeft: '5px' }}>生效时间</span>
+                  </div>
+                  <div class={styles.timer}>
+                    {orderStatus.vipInfo.startTime} 至{' '}
+                    {orderStatus.vipInfo.endTime}
+                  </div>
+                </div>
+              )
+            }}
+          />
+        </CellGroup>
       </div>
       // 视频课
     )

+ 1 - 0
src/views/order-detail/orderStatus.ts

@@ -6,6 +6,7 @@ export const orderStatus = reactive({
   videoInfo: {} as any, // 视频购买信息
   practiceInfo: {} as any, // 视频购买信息
   vipInfo: {} as any, // 会员购买信息
+  musicInfo: {} as any, // 单曲购买信息
   orderInfo: {
     // 订单信息
     orderNo: '',