Explorar o código

更新跳转与详情

lex %!s(int64=2) %!d(string=hai) anos
pai
achega
1be689129e

+ 11 - 15
src/components/col-video/index.tsx

@@ -79,15 +79,13 @@ export default defineComponent({
       }
     })
   },
-  beforeUnmount(){
-    postMessage(
-      {
-        api: 'limitScreenRecord',
-        content: {
-          type: 0
-        }
+  beforeUnmount() {
+    postMessage({
+      api: 'limitScreenRecord',
+      content: {
+        type: 0
       }
-    )
+    })
   },
   computed: {
     computedSeeStatus() {
@@ -202,14 +200,12 @@ export default defineComponent({
             }
           }
         )
-        postMessage(
-          {
-            api: 'limitScreenRecord',
-            content: {
-              type: 1
-            }
+        postMessage({
+          api: 'limitScreenRecord',
+          content: {
+            type: 1
           }
-        )
+        })
 
         this.onPlay && this.onPlay()
       })

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

@@ -46,6 +46,14 @@ const noLoginRouter = [
     meta: {
       title: '分享专辑'
     }
+  },
+  {
+    path: '/shareMusic',
+    name: 'shareMusic',
+    component: () => import('@/teacher/share-page/share-music/index'),
+    meta: {
+      title: '分享专辑'
+    }
   }
 ]
 

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

@@ -5,7 +5,6 @@ import styles from './music.module.less'
 import MusicList from '@/views/music/list'
 import { state } from '@/state'
 import { orderStatus } from '@/views/order-detail/orderStatus'
-import { musicBuy } from '@/views/music/music'
 
 export default defineComponent({
   name: 'music',
@@ -25,13 +24,11 @@ export default defineComponent({
   },
   methods: {
     onItemClick(item: any) {
-      musicBuy(item, path => {
-        this.$router.push({
-          path,
-          query: {
-            orderType: 'MUSIC'
-          }
-        })
+      this.$router.push({
+        path: '/music-detail',
+        query: {
+          id: item.id
+        }
       })
     }
   },

+ 436 - 0
src/teacher/share-page/share-music/index.module.less

@@ -0,0 +1,436 @@
+.base > div {
+  background: url(./header-bg.png) no-repeat top center;
+  // background-attachment: fixed;
+}
+.detail {
+  overflow: hidden;
+  --van-nav-bar-background-color: transparent;
+  --van-nav-bar-icon-color: #fff;
+  --van-nav-bar-text-color: #fff;
+  --van-nav-bar-title-text-color: #fff;
+
+  // color: #4a5464
+  --plyr-color-main: var(--van-primary);
+  --plyr-control-icon-size: 12px;
+
+  :global {
+    .plyr__controls .plyr__controls__item:first-child {
+      background-color: var(--van-primary);
+      color: #fff;
+      border-radius: 50%;
+    }
+  }
+}
+.base {
+  :global(.van-sticky--fixed) {
+    box-shadow: 10px 10px 10px var(--box-shadow-color);
+  }
+}
+.shareBtn {
+  display: flex;
+  align-items: flex-start;
+  color: #fff;
+  font-size: 14px;
+  line-height: 20px !important;
+  :global(.van-image) {
+    width: 18px;
+    height: 18px;
+    margin-right: 6px;
+  }
+}
+
+.bgImg {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 265px;
+  object-fit: cover;
+  filter: blur(10px);
+}
+.bgContent {
+  position: absolute;
+  top: 0;
+  height: 265px;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  backdrop-filter: blur(20px);
+  -webkit-backdrop-filter: blur(20px);
+}
+.musicContent {
+  position: relative;
+  width: 100%;
+  height: 500px;
+  overflow: hidden;
+  &::after {
+    content: ' ';
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background: linear-gradient(
+      180deg,
+      rgba(255, 255, 255, 0) 0%,
+      #ffffff 100%
+    );
+    height: 287px;
+  }
+  .musicTitle {
+    text-align: center;
+    font-size: 16px;
+  }
+  .musicImg {
+    width: 100%;
+  }
+  .finch {
+    width: 150px;
+    margin: 80px auto 0;
+  }
+  .finchLoad {
+    text-align: center;
+    color: #333;
+    font-size: 15px;
+    margin-top: 4px;
+  }
+  :global {
+    iframe {
+      visibility: hidden;
+      body {
+        ::-webkit-scrollbar-thumb {
+          background-color: #efeff0;
+          border: 1px solid transparent;
+          background-clip: padding-box;
+          border-radius: 5px;
+        }
+      }
+    }
+  }
+}
+
+.collectCell {
+  margin: 10px 16px 0;
+  background: #ffffff;
+  border-radius: 10px;
+  box-shadow: 0px 0px 6px 0px rgba(229, 229, 229, 0.7);
+  overflow: hidden;
+}
+.videoOperation {
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 5px;
+  z-index: 1;
+}
+.audition {
+  display: flex;
+  align-items: center;
+  margin: 10px 16px 0;
+  background: linear-gradient(180deg, #fff0d9 0%, #ffdfb8 100%);
+  border-radius: 16px;
+  padding: 7px 12px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #ff731d;
+  height: 18px;
+  img {
+    margin-top: -2px;
+    width: 21px;
+    height: 15px;
+    margin-right: 11px;
+  }
+}
+.collect {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 11px;
+  font-size: 14px;
+  color: #666666;
+  .userInfo {
+    display: flex;
+    align-items: center;
+    padding: 3px 6px;
+    background: #d5f3ee;
+    border-radius: 16px;
+    img {
+      width: 26px;
+      height: 26px;
+      border-radius: 50%;
+    }
+    span {
+      padding-left: 8px;
+      max-width: 120px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      color: #2dc7aa;
+    }
+  }
+  .collectSection {
+    display: flex;
+    align-items: center;
+    img {
+      margin-top: -2px;
+      margin-left: 11px;
+      width: 18px;
+      height: 18px;
+    }
+  }
+}
+
+.lookAlbum {
+  padding: 17px 20px;
+  margin-bottom: 15px;
+  font-size: 14px;
+  color: #333333;
+  line-height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  & > div {
+    display: flex;
+    align-items: center;
+    line-height: 1;
+    img {
+      width: 20px;
+      height: 20px;
+      margin-right: 10px;
+    }
+  }
+}
+
+.bg {
+  position: relative;
+  height: 100%;
+  padding: 16px;
+  z-index: 11;
+}
+.musicContainer {
+  position: relative;
+  // padding: 16px 0 0;
+  z-index: 12;
+  border-radius: 18px 18px 0 0;
+  background-color: #fff;
+  overflow-y: auto;
+}
+
+.shareMate {
+  position: relative;
+  margin-top: 50px;
+  display: flex;
+  flex: 1;
+  align-items: center;
+  padding: 11px 12px;
+  background: #ffffff;
+  border-radius: 10px;
+  // border: 1px solid #2dc7aa;
+  .icon {
+    width: 36px;
+    height: 36px;
+    border-radius: 10px;
+  }
+  .info {
+    margin-left: 14px;
+    flex: 1;
+    margin-right: 14px;
+    word-break: break-all;
+    > h4 {
+      color: var(--music-list-item-title-color);
+      font-size: 14px;
+      font-weight: 600;
+      width: 200px;
+    }
+    > p {
+      color: var(--music-list-item-mate-color);
+      line-height: 17px;
+    }
+  }
+
+  .tagDiscount {
+    position: absolute;
+    top: -26px;
+    left: 15px;
+    padding: 2px 10px 1px;
+    height: 23px;
+    background: linear-gradient(180deg, #ffb635 0%, #ff4e18 100%);
+    border-radius: 8px 8px 0px 0px;
+    font-size: 14px;
+    font-weight: 600;
+    color: #ffffff;
+    line-height: 20px;
+  }
+}
+
+.shareVip {
+  position: relative;
+  margin-top: 35px;
+  display: flex;
+  flex: 1;
+  align-items: center;
+  padding: 7px;
+  background: #ffffff;
+  border-radius: 10px;
+
+  .icon {
+    width: 72px;
+    height: 72px;
+    border-radius: 10px;
+  }
+  .info {
+    margin-left: 6px;
+    flex: 1;
+    word-break: break-all;
+    > h4 {
+      color: var(--music-list-item-title-color);
+      font-size: 16px;
+      font-weight: 600;
+    }
+    > p {
+      color: var(--music-list-item-mate-color);
+      line-height: 17px;
+    }
+  }
+}
+
+.tagDiscount {
+  position: absolute;
+  top: -23px;
+  left: 15px;
+  padding: 0 10px;
+  height: 23px;
+  background: linear-gradient(180deg, #ffb635 0%, #ff4e18 100%);
+  border-radius: 8px 8px 0px 0px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
+  line-height: 24px;
+}
+.buttonDiscount {
+  position: absolute;
+  top: -23px;
+  right: 15px;
+  padding: 0 10px;
+  height: 23px;
+  background: linear-gradient(180deg, #ffb635 0%, #ff4e18 100%);
+  border-radius: 8px 8px 0px 0px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
+  line-height: 24px;
+}
+
+.pImg {
+  width: 46px;
+  height: 46px;
+  border-radius: 10px;
+  overflow: hidden;
+  flex-shrink: 0;
+}
+.musicInfo {
+  padding-top: 23px !important;
+  padding-bottom: 23px !important;
+  margin-bottom: 10px;
+  .tag {
+    flex-shrink: 0;
+    // padding: 2px 4px 0;
+    border-radius: 4px;
+  }
+
+  .info {
+    margin-left: 14px;
+    flex: 1;
+    margin-right: 14px;
+    word-break: break-all;
+    > h4 {
+      font-size: 16px;
+      font-weight: bold;
+      color: #1a1a1a;
+      width: 200px;
+      padding-bottom: 3px;
+    }
+    > p {
+      font-size: 12px;
+      color: #999;
+      line-height: 17px;
+    }
+  }
+
+  .download {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    font-size: 12px;
+    img {
+      height: 24px;
+      width: 24px;
+    }
+  }
+
+  .exquisiteFlag {
+    width: 14px;
+    margin-left: 5px;
+    flex-shrink: 0;
+  }
+  .songAlbum {
+    width: 15px;
+    height: 15px;
+    margin-left: 5px;
+    flex-shrink: 0;
+  }
+}
+
+.colSticky {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  // height: 45px;
+  .priceSection {
+    font-size: 14px;
+    font-weight: 400;
+    color: #999999;
+    .price {
+      font-size: 22px;
+      font-weight: bold;
+      color: #ff4e19;
+      i {
+        font-style: normal;
+        font-size: 16px;
+      }
+    }
+  }
+}
+
+.buyBtn {
+  --van-button-default-height: 38px;
+  :global {
+    .van-button {
+      padding: 0 22px;
+      font-weight: 600;
+
+      & + .van-button {
+        margin-left: 12px;
+      }
+    }
+  }
+  .primry {
+    box-shadow: 0px 2px 7px 0px rgba(45, 199, 170, 0.25);
+  }
+  .member {
+    box-shadow: 0px 2px 7px 0px rgba(187, 156, 83, 0.25);
+  }
+}
+
+.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%;
+  }
+}

+ 487 - 0
src/teacher/share-page/share-music/index.tsx

@@ -0,0 +1,487 @@
+import {
+  computed,
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref
+} from 'vue'
+import umiRequest from 'umi-request'
+import { useRoute, useRouter } from 'vue-router'
+import request from '@/helpers/request'
+import ColHeader from '@/components/col-header'
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
+import {
+  Button,
+  Cell,
+  Dialog,
+  Icon,
+  Image,
+  Popup,
+  Sticky,
+  Tag,
+  Toast
+} from 'vant'
+import styles from './index.module.less'
+// import Item from '../list/item'
+import { useRect } from '@vant/use'
+import { Vue3Lottie } from 'vue3-lottie'
+import { getRandomKey } from '@/views/music/music'
+import { state } from '@/state'
+import { useEventTracking } from '@/helpers/hooks'
+import ColSticky from '@/components/col-sticky'
+import { browser, moneyFormat } from '@/helpers/utils'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import iconAlbum from '@/views/music/component/images/icon_album.png'
+import iconDownload from '@/views/music/music-detail/images/icon_download.png'
+import AstronautJSON from '@/views/music/music-detail/animate/bigLoad.json'
+import ColShare from '@/components/col-share'
+import iconCollect from '@/views/music/music-detail/images/icon_collect.png'
+import iconCollectActive from '@/views/music/music-detail/images/icon_collect_active.png'
+import iconListen from '@/views/music/music-detail/images/icon_listen.png'
+import iconTeacher from '@common/images/icon_teacher.png'
+import {
+  addMusicTitle,
+  addWatermark,
+  convasToImg,
+  imgToCanvas
+} from '@/views/music/music-detail/imageFunction'
+import Plyr from 'plyr'
+import 'plyr/dist/plyr.css'
+
+import icon_exquisite from '@/views/music/component/images/icon_exquisite.png'
+import icon_album_active from '@/views/music/component/images/icon_album_active.png'
+import wx_bg from '../images/wx_bg.png'
+import { initJumpNativePage, shareCall } from '../share'
+import qs from 'query-string'
+
+export default defineComponent({
+  name: 'MusicDetail',
+  setup() {
+    localStorage.setItem('behaviorId', getRandomKey())
+    const route = useRoute()
+    const loading = ref(false)
+    const isError = ref(false)
+    const headers = ref(null)
+    const footers = ref(null)
+    const heightInfo = ref<any>('0')
+    const musicDetail = ref<any>(null)
+    const showImg = ref<string>('')
+    const accompanyUrl = ref<string>('')
+    const wxStatus = ref<boolean>(false)
+
+    const tmpUrl = `${location.origin}/student/#/music-detail?${qs.stringify(
+      route.query
+    )}`
+    const jumpUrl = ref<string>(tmpUrl)
+
+    const colors: any = {
+      FREE: {
+        color: '#01B84F',
+        text: '免费'
+      },
+      VIP: {
+        color: '#CD863E',
+        text: '会员'
+      },
+      CHARGE: {
+        color: '#3591CE',
+        text: '点播'
+      }
+    }
+
+    const FetchList = async (id?: any) => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const search = route.query
+        const res = await request.post(`/open/musicShareProfit`, {
+          prefix: '/api-teacher',
+          requestType: 'json',
+          data: {
+            bizId: search.id,
+            userId: search.recomUserId
+          }
+        })
+        musicDetail.value = res.data.musicSheet
+        showImg.value = musicDetail.value?.musicImg || ''
+
+        if (!showImg.value) {
+          setAccompanyUrl()
+          window.addEventListener(
+            'message',
+            async e => {
+              // 给图片设置背景色
+              const tempCanvas = await imgToCanvas(e.data)
+              const img = convasToImg(tempCanvas)
+              //  开始上传图片
+              uploadFunction(img)
+            },
+            false
+          )
+        }
+      } catch (error) {
+        isError.value = true
+      }
+      loading.value = false
+    }
+    const base64ToBlob = data => {
+      const arr = data.split(','),
+        mime = arr[0].match(/:(.*?);/)[1]
+
+      const bstr = atob(arr[1])
+      let n = bstr.length
+      const u8arr = new Uint8Array(n)
+
+      while (n--) {
+        u8arr[n] = bstr.charCodeAt(n)
+      }
+      return new Blob([u8arr], { type: mime })
+    }
+    const uploadFunction = async file => {
+      try {
+        const formData = new FormData()
+        const fileName =
+          new Date().getTime() +
+          musicDetail.value?.musicSheetName.replaceAll(' ', '_') +
+          '.png'
+        const keyTime = new Date().getTime() + fileName
+        const obj = {
+          filename: fileName,
+          bucketName: 'cloud-coach',
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: keyTime,
+            unknowValueField: []
+          }
+        }
+
+        const res = await request.post(state.platformApi + '/getUploadSign', {
+          data: obj
+        })
+
+        Toast.loading({
+          message: '加载中...',
+          forbidClick: true,
+          loadingType: 'spinner',
+          duration: 0
+        })
+
+        const dataObj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: keyTime,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+        for (const key in dataObj) {
+          formData.append(key, dataObj[key])
+        }
+        const files = base64ToBlob(file)
+
+        formData.append('file', files, fileName)
+        const ossUploadUrl = 'https://ks3-cn-beijing.ksyuncs.com/cloud-coach'
+        await umiRequest(ossUploadUrl, {
+          method: 'POST',
+          data: formData
+        })
+        Toast.clear()
+        const imgurl = ossUploadUrl + '/' + keyTime
+
+        await request.post(state.platformApi + '/open/music/sheet/img', {
+          data: { musicSheetId: musicDetail.value.id, musicImg: imgurl }
+        })
+        showImg.value = imgurl
+      } catch (e) {
+        console.log(e)
+      }
+    }
+
+    const setAccompanyUrl = () => {
+      const url = 'http://dev.colexiu.com'
+      const music = musicDetail.value
+      let subjectId = ''
+      if (music.background && music.background.length > 0) {
+        subjectId = music.background[0].id
+      }
+      accompanyUrl.value =
+        url +
+        `/accompany/colxiu-website.html?id=${music.id}&part-index=${subjectId}`
+    }
+    const player = ref<any>(null)
+    const audio = ref<any>(null)
+    const freeRate = ref<any>(0)
+    const initAudio = async () => {
+      const config = await request.get(
+        state.platformApi + '/sysConfig/queryByParamNameList',
+        {
+          params: {
+            paramNames: 'music_sheet_free_rate'
+          }
+        }
+      )
+      freeRate.value = config.data[0]?.paramValue || 0
+
+      const controls = [
+        // 'play-large',
+        'play',
+        'progress',
+        // 'captions',
+        // 'fullscreen',
+        'duration'
+      ]
+      player.value = new Plyr(audio.value, {
+        controls: controls
+      })
+
+      player.value.on('timeupdate', () => {
+        // 允许播放时间
+        const players = player.value
+        const playTime = (players.duration * freeRate.value) / 100 || 0
+        // 时间,是否购买,是否免费
+        if (
+          players.currentTime >= playTime &&
+          musicDetail.value?.orderStatus !== 'PAID' &&
+          !paymentType.value.includes('FREE')
+        ) {
+          // players.stop()
+          players.pause()
+        }
+      })
+    }
+    onMounted(async () => {
+      initJumpNativePage(jumpUrl.value)
+      await FetchList()
+      const { height } = useRect(headers as any)
+      const footer = useRect(footers as any)
+      heightInfo.value = height + footer.height
+
+      // 初始化音频
+      if (musicDetail.value?.audioFileUrl) {
+        initAudio()
+      }
+    })
+
+    const paymentType = computed(() => {
+      let paymentType = musicDetail.value?.paymentType
+      if (typeof paymentType === 'string') {
+        paymentType = paymentType.split(',')
+        return paymentType
+      }
+      return []
+    })
+
+    const onShare = () => {
+      console.log(browser().weixin)
+      if (browser().weixin) {
+        wxStatus.value = true
+        return
+      }
+      // 尝试拉起app
+      shareCall(jumpUrl.value)
+      // 不管有没有拉起app则都跳转到下载app
+      setTimeout(() => {
+        window.location.href = location.origin + '/student/#/download'
+      }, 3000)
+    }
+
+    return () => {
+      return (
+        <div class={styles.detail}>
+          <Sticky position="top">
+            <div ref={headers}>
+              <ColHeader
+                background="transparent"
+                border={false}
+                isFixed={false}
+                color="#fff"
+                backIconColor="white"
+              />
+            </div>
+          </Sticky>
+
+          <img class={styles.bgImg} src={musicDetail.value?.titleImg} />
+          <div class={styles.bgContent}></div>
+          <div
+            class={styles.musicContainer}
+            style={{
+              marginTop: '16px',
+              height: `calc(100vh - ${heightInfo.value + 16 + 'px'})`
+            }}
+          >
+            <Cell
+              border={false}
+              center
+              class={styles.musicInfo}
+              v-slots={{
+                icon: () => (
+                  <Image
+                    class={styles.pImg}
+                    src={musicDetail.value?.titleImg}
+                  />
+                ),
+                title: () => (
+                  <div class={styles.info}>
+                    <h4 class="van-ellipsis">
+                      {musicDetail.value?.musicSheetName}
+                    </h4>
+                    <p
+                      style={{
+                        display: 'flex'
+                      }}
+                    >
+                      {paymentType.value.map(tag => (
+                        <Tag
+                          style={{ color: colors[tag].color }}
+                          class={styles.tag}
+                          type="success"
+                          plain
+                        >
+                          {colors[tag].text}
+                        </Tag>
+                      ))}
+                      {musicDetail.value?.exquisiteFlag === 1 && (
+                        <Image
+                          class={styles.exquisiteFlag}
+                          src={icon_exquisite}
+                        />
+                      )}
+
+                      {musicDetail.value?.albumNums > 0 && (
+                        <Image
+                          class={styles.songAlbum}
+                          src={icon_album_active}
+                        />
+                      )}
+                      <span style={{ paddingTop: '2px', paddingLeft: '6px' }}>
+                        {musicDetail.value?.composer}
+                      </span>
+                    </p>
+                  </div>
+                ),
+                value: () => (
+                  <span class={styles.download} onClick={() => onShare()}>
+                    <img src={iconDownload} />
+                    下载曲谱
+                  </span>
+                )
+              }}
+            />
+            <div class={styles.musicContent}>
+              <iframe
+                id="containerPrint"
+                ref="print"
+                style="width: 100%;page-break-after:always; height: 0"
+                src={accompanyUrl.value}
+              />
+              <p class={styles.musicTitle}>
+                {musicDetail.value?.musicSheetName}
+              </p>
+              {showImg.value ? (
+                <img src={showImg.value} alt="" class={styles.musicImg} />
+              ) : (
+                <>
+                  <Vue3Lottie
+                    animationData={AstronautJSON}
+                    class={styles.finch}
+                  ></Vue3Lottie>
+                  <p class={styles.finchLoad}>加载中...</p>
+                </>
+              )}
+
+              <div class={styles.videoOperation}>
+                {musicDetail.value?.audioFileUrl && (
+                  <>
+                    {(paymentType.value.includes('CHARGE') ||
+                      paymentType.value.includes('VIP')) &&
+                      musicDetail.value?.orderStatus !== 'PAID' && (
+                        <div class={[styles.audition]}>
+                          <img src={iconListen} />
+                          <span>每首曲目可试听{freeRate.value}%</span>
+                        </div>
+                      )}
+
+                    <div class={[styles.audio, styles.collectCell]}>
+                      <audio id="player" controls ref={audio}>
+                        <source
+                          src={musicDetail.value?.audioFileUrl}
+                          type="audio/mp3"
+                        />
+                      </audio>
+                    </div>
+                  </>
+                )}
+
+                <div class={[styles.collect, styles.collectCell]}>
+                  <div class={[styles.userInfo]}>
+                    <img src={musicDetail.value?.userAvatar || iconTeacher} />
+                    <span>{musicDetail.value?.userName}</span>
+                  </div>
+
+                  <div
+                    class={[styles.collectSection]}
+                    onClick={() => onShare()}
+                  >
+                    <span>{musicDetail.value?.favoriteCount}人收藏</span>
+                    <img
+                      src={
+                        musicDetail.value?.favorite
+                          ? iconCollectActive
+                          : iconCollect
+                      }
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div
+              class={[styles.lookAlbum, styles.collectCell]}
+              onClick={() => {
+                onShare()
+              }}
+            >
+              <div>
+                <img src={iconAlbum} />
+                <span>进入曲目所在专辑列表</span>
+              </div>
+              <Icon name="arrow" size={16} color="#666" />
+            </div>
+          </div>
+
+          {musicDetail.value?.id && (
+            <ColSticky position="bottom" background="white">
+              <div ref={footers}>
+                <Button
+                  round
+                  block
+                  type="primary"
+                  color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
+                  onClick={() => onShare()}
+                >
+                  下载酷乐秀进入详情
+                </Button>
+              </div>
+            </ColSticky>
+          )}
+
+          {wxStatus.value && (
+            <div
+              class={styles.wxpopup}
+              onClick={() => {
+                wxStatus.value = false
+              }}
+            >
+              <img src={wx_bg} alt="" />
+            </div>
+          )}
+        </div>
+      )
+    }
+  }
+})

+ 6 - 5
src/teacher/share-page/share-video/index.tsx

@@ -42,14 +42,13 @@ export default defineComponent({
     if (browser().isApp) {
       if (state.platformType === 'STUDENT') {
         const query = this.$route.query
-        query.recomUserId = query.userType && query.userType == 'STUDENT' ? '' : query.recomUserId
+        query.recomUserId =
+          query.userType && query.userType == 'STUDENT' ? '' : query.recomUserId
         // 自动跳转到学生端视频课详情购买页
         // 为了处理andoird webview的跳转问题
         if (browser().ios) {
           window.location.replace(
-            `${location.origin}/student/#/videoDetail?${qs.stringify(
-              query
-            )}`
+            `${location.origin}/student/#/videoDetail?${qs.stringify(query)}`
           )
         } else {
           postMessage({
@@ -120,7 +119,9 @@ export default defineComponent({
       const { origin } = location
       let str = origin + '/student/#/videoDetail'
       const params = this.$route.query
-      str += `?recomUserId=${params.userType && params.userType == 'STUDENT' ? '' : this.recomUserId}&groupId=${params.groupId}`
+      str += `?recomUserId=${
+        params.userType && params.userType == 'STUDENT' ? '' : this.recomUserId
+      }&groupId=${params.groupId}`
       console.log(str)
       shareCall(str, {})
     },

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

@@ -78,7 +78,9 @@ export default defineComponent({
         })
         const { musicSheetList, ...rest } = res.data
         rows.value = [...musicSheetList.rows]
-        const musicTagNames = rest?.musicTagNames?.split(',') || []
+        const musicTagNames = rest?.musicTagNames
+          ? rest?.musicTagNames?.split(',')
+          : []
         albumDetail.value = {
           ...rest,
           musicTagNames

+ 0 - 220
src/views/music/component/song/index.tsx

@@ -51,47 +51,6 @@ export default defineComponent({
       }
     }
 
-    const toggleFavorite = async () => {
-      try {
-        await request.post('/music/sheet/favorite/' + moreData.value.id, {
-          prefix:
-            state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
-        })
-        moreData.value.favorite = moreData.value.favorite ? 0 : 1
-        setTimeout(() => {
-          Toast(moreData.value.favorite ? '收藏成功' : '取消收藏成功')
-          isMore.value = false
-        }, 100)
-      } catch (error) {}
-    }
-
-    const shareStatus = ref(false)
-    const shareUrl = ref('')
-    const shareDiscount = ref(0)
-    // console.log(data)
-    const onShare = async () => {
-      try {
-        const res = await request.post('/api-teacher/open/musicShareProfit', {
-          data: {
-            bizId: moreData.value.id,
-            userId: state.user.data?.userId
-          }
-        })
-        let url =
-          location.origin +
-          `/accompany/colexiu-share.html?id=${moreData.value.id}&recomUserId=${state.user.data?.userId}&userType=${state.platformType}`
-        // 判断是否有活动
-        if (res.data.discount === 1) {
-          url += `&activityId=${res.data.activityId}`
-        }
-        shareDiscount.value = res.data.discount || 0
-        shareUrl.value = url
-        isMore.value = false
-        shareStatus.value = true
-        return
-      } catch {}
-    }
-
     const list = computed(() => {
       return props.list.map(n => {
         if (typeof n.paymentType === 'string')
@@ -164,185 +123,6 @@ export default defineComponent({
               </div>
             </div>
           ))}
-
-          <Popup
-            v-model:show={isMore.value}
-            position="bottom"
-            round
-            teleport="body"
-          >
-            <CellGroup border={false}>
-              <Cell
-                center
-                class={styles.musicInfo}
-                v-slots={{
-                  icon: () => (
-                    <Image class={styles.pImg} src={moreData?.value.titleImg} />
-                  ),
-                  title: () => (
-                    <div class={styles.info}>
-                      <h4 class="van-ellipsis">
-                        {moreData?.value.musicSheetName}
-                      </h4>
-                      <p
-                        style={{
-                          display: 'flex'
-                        }}
-                      >
-                        <span style={{ paddingTop: '2px' }}>
-                          {moreData?.value.composer}
-                        </span>
-                      </p>
-                    </div>
-                  )
-                }}
-              />
-
-              <Cell
-                border={false}
-                size="large"
-                title={moreData.value.favorite ? '取消收藏' : '收藏曲目'}
-                center
-                onClick={() => toggleFavorite()}
-                v-slots={{
-                  icon: () => (
-                    <div class={styles.shareIcon}>
-                      <Image
-                        src={
-                          moreData.value.favorite
-                            ? collection_active
-                            : collection
-                        }
-                      />
-                    </div>
-                  )
-                }}
-              />
-              <Cell
-                border={false}
-                size="large"
-                title={'分享曲目'}
-                center
-                onClick={() => onShare()}
-                v-slots={{
-                  icon: () => (
-                    <div class={styles.shareIcon}>
-                      <Image
-                        class={styles.share}
-                        src={getAssetsHomeFile('icon_share.png')}
-                      />
-                    </div>
-                  )
-                }}
-              />
-
-              <Cell
-                border={false}
-                size="large"
-                center
-                title={`作曲:${moreData.value?.composer}`}
-                v-slots={{
-                  icon: () => (
-                    <div class={styles.shareIcon}>
-                      <Image src={getAssetsHomeFile('icon_author.png')} />
-                    </div>
-                  )
-                }}
-              />
-              <Cell
-                border={false}
-                size="large"
-                center
-                title={`上传:${moreData.value?.addName || '--'}`}
-                v-slots={{
-                  icon: () => (
-                    <div class={styles.shareIcon}>
-                      <Image src={getAssetsHomeFile('icon_uploader.png')} />
-                    </div>
-                  )
-                }}
-              />
-              <Cell
-                border={false}
-                size="large"
-                title={'小酷Ai练习'}
-                isLink
-                center
-                class={moreData.value?.albumNums <= 0 && styles.mb100}
-                onClick={() => {
-                  isMore.value = false
-                  emit('detail', moreData.value)
-                }}
-                v-slots={{
-                  icon: () => (
-                    <div class={styles.shareIcon}>
-                      <Image src={getAssetsHomeFile('icon_ai.png')} />
-                    </div>
-                  )
-                }}
-              />
-              {moreData.value?.albumNums > 0 && (
-                <Cell
-                  border={false}
-                  size="large"
-                  title={'查看所在专辑列表'}
-                  isLink
-                  center
-                  class={styles.mb100}
-                  onClick={() => {
-                    isMore.value = false
-                    router.push({
-                      path: '/look-album-list',
-                      query: {
-                        id: moreData.value?.id
-                      }
-                    })
-                  }}
-                  v-slots={{
-                    icon: () => (
-                      <div class={styles.shareIcon}>
-                        <Image src={getAssetsHomeFile('icon_album.png')} />
-                      </div>
-                    )
-                  }}
-                />
-              )}
-            </CellGroup>
-          </Popup>
-
-          <Popup
-            v-model:show={shareStatus.value}
-            style={{ background: 'transparent' }}
-            teleport="body"
-          >
-            <ColShare
-              teacherId={moreData.value?.userId}
-              shareUrl={shareUrl.value}
-              shareType="music"
-            >
-              <div class={styles.shareMate}>
-                {shareDiscount.value === 1 && (
-                  <div class={styles.tagDiscount}>专属优惠</div>
-                )}
-
-                <img
-                  class={styles.icon}
-                  crossorigin="anonymous"
-                  src={
-                    moreData.value?.titleImg +
-                      `@base@tag=imgScale&h=80&w=80&m=1?t=${+new Date()}` ||
-                    MusicIcon
-                  }
-                />
-                <div class={styles.info}>
-                  <h4 class="van-multi-ellipsis--l2">
-                    {moreData.value?.musicSheetName}
-                  </h4>
-                  <p>作曲人:{moreData.value?.composer}</p>
-                </div>
-              </div>
-            </ColShare>
-          </Popup>
         </div>
       )
     }

+ 7 - 2
src/views/music/list/index.tsx

@@ -7,7 +7,7 @@ import SelectTag from '../search/select-tag'
 import { useRoute, useRouter } from 'vue-router'
 import ColResult from '@/components/col-result'
 import styles from './index.module.less'
-import { getRandomKey, musicBuy } from '../music'
+import { getRandomKey } from '../music'
 import { state as baseState } from '@/state'
 import SelectSubject from '../search/select-subject'
 import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
@@ -295,7 +295,12 @@ export default defineComponent({
                   list={data.value.rows}
                   onDetail={(item: any) => {
                     if (onItemClick === noop) {
-                      musicBuy(item)
+                      router.push({
+                        path: '/music-detail',
+                        query: {
+                          id: item.id
+                        }
+                      })
                     } else {
                       onItemClick?.(item)
                     }

+ 7 - 3
src/views/music/list/list.tsx

@@ -6,7 +6,6 @@ import { List } from 'vant'
 import { defineComponent, reactive, ref } from 'vue'
 import { useRoute } from 'vue-router'
 import Song from '../component/song'
-import { getRandomKey, musicBuy } from '../music'
 import styles from './index.module.less'
 
 const noop = () => {}
@@ -33,7 +32,7 @@ export default defineComponent({
   },
   setup({ hideSearch, defauleParams, onItemClick, teacherId }, { expose }) {
     const route = useRoute()
-    // const router = useRouter()
+    const router = useRouter()
     const tempParams: any = {}
     if (state.version) {
       tempParams.version = state.version || '' // 处理ios审核版本
@@ -108,7 +107,12 @@ export default defineComponent({
               list={data.value.rows}
               onDetail={(item: any) => {
                 if (onItemClick === noop) {
-                  musicBuy(item)
+                  router.push({
+                    path: '/music-detail',
+                    query: {
+                      id: item.id
+                    }
+                  })
                 } else {
                   onItemClick?.(item)
                 }

+ 84 - 0
src/views/music/music-detail/imageFunction.ts

@@ -0,0 +1,84 @@
+import imgList from './images/logoWatermark.png'
+export const imgToCanvas = async (url: string) => {
+  console.log('imgToCanvas', url)
+  const img = document.createElement('img')
+  img.setAttribute('crossOrigin', 'anonymous')
+  img.src = url + `?${new Date().getTime()}`
+
+  // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
+  await new Promise(resolve => (img.onload = resolve))
+  // 创建canvas DOM元素,并设置其宽高和图片一样
+
+  const canvas = document.createElement('canvas')
+  canvas.width = img.width
+  canvas.height = img.height
+  // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
+  const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
+
+  ctx.fillStyle = 'rgb(255, 255, 255)'
+  ctx.fillStyle = '#fff'
+  ctx.fillRect(0, 0, img.width, img.height)
+  ctx.drawImage(img, 0, 0)
+  return canvas
+}
+
+/* canvas添加水印
+
+* @param {canvas对象} canvas
+
+* @param {水印文字} text
+
+*/
+
+export const addWatermark = async (canvas, text) => {
+  console.log('addWatermark')
+  try {
+    const ctx = canvas.getContext('2d')
+    // ctx.fillStyle = '#fff'
+    const img = document.createElement('img')
+    img.setAttribute('crossOrigin', 'anonymous')
+    img.src = imgList + `?${new Date().getTime()}`
+    // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
+    await new Promise(resolve => (img.onload = resolve))
+    // 创建canvas DOM元素,并设置其宽高和图片一样
+    const water = document.createElement('canvas')
+    water.width = 600
+    water.height = 500
+
+    // 第一层
+    const waterCtx = water.getContext('2d') as CanvasRenderingContext2D
+    waterCtx.clearRect(0, 0, water.width, water.height)
+    // 小水印中文字偏转角度
+    waterCtx.rotate((-30 * Math.PI) / 180)
+    waterCtx.drawImage(img, 0, 300)
+    const pat = ctx.createPattern(water, 'repeat')
+    ctx.fillStyle = pat
+    ctx.fillRect(0, 0, canvas.width, canvas.height)
+
+    return canvas
+  } catch (e) {
+    console.log(e)
+  }
+}
+
+export const addMusicTitle = (canvas, info) => {
+  canvas.getContext('2d')
+  const water = document.createElement('canvas')
+
+  // 小水印画布大小
+  water.width = canvas.width
+  water.height = canvas.height + 30
+  const waterCtx = water.getContext('2d') as CanvasRenderingContext2D
+  waterCtx.fillStyle = '#fff'
+  waterCtx.fillRect(0, 0, canvas.width, canvas.height + 70)
+  waterCtx.font = `40pt Calibri`
+  waterCtx.fillStyle = '#000'
+  waterCtx.textAlign = 'center'
+  waterCtx.drawImage(canvas, 0, 70)
+  waterCtx.fillText(info.title, canvas.width / 2, 120)
+  return water
+}
+
+export const convasToImg = canvas => {
+  return canvas.toDataURL('image/png')
+}

BIN=BIN
src/views/music/music-detail/images/logoWatermark.png


+ 61 - 5
src/views/music/music-detail/index.module.less

@@ -8,6 +8,18 @@
   --van-nav-bar-icon-color: #fff;
   --van-nav-bar-text-color: #fff;
   --van-nav-bar-title-text-color: #fff;
+
+  // color: #4a5464
+  --plyr-color-main: var(--van-primary);
+  --plyr-control-icon-size: 12px;
+
+  :global {
+    .plyr__controls .plyr__controls__item:first-child {
+      background-color: var(--van-primary);
+      color: #fff;
+      border-radius: 50%;
+    }
+  }
 }
 .base {
   :global(.van-sticky--fixed) {
@@ -36,6 +48,15 @@
   object-fit: cover;
   filter: blur(10px);
 }
+.bgContent {
+  position: absolute;
+  top: 0;
+  height: 265px;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  backdrop-filter: blur(20px);
+  -webkit-backdrop-filter: blur(20px);
+}
 .musicContent {
   position: relative;
   width: 100%;
@@ -91,6 +112,7 @@
   background: #ffffff;
   border-radius: 10px;
   box-shadow: 0px 0px 6px 0px rgba(229, 229, 229, 0.7);
+  overflow: hidden;
 }
 .videoOperation {
   position: absolute;
@@ -156,6 +178,27 @@
   }
 }
 
+.lookAlbum {
+  padding: 17px 20px;
+  margin-bottom: 15px;
+  font-size: 14px;
+  color: #333333;
+  line-height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  & > div {
+    display: flex;
+    align-items: center;
+    line-height: 1;
+    img {
+      width: 20px;
+      height: 20px;
+      margin-right: 10px;
+    }
+  }
+}
+
 .bg {
   position: relative;
   height: 100%;
@@ -264,16 +307,16 @@
 }
 .buttonDiscount {
   position: absolute;
-  top: -23px;
+  top: -18px;
   right: 15px;
-  padding: 0 10px;
-  height: 23px;
+  padding: 0 5px;
+  height: 18px;
   background: linear-gradient(180deg, #ffb635 0%, #ff4e18 100%);
   border-radius: 8px 8px 0px 0px;
-  font-size: 14px;
+  font-size: 12px;
   font-weight: 600;
   color: #ffffff;
-  line-height: 24px;
+  line-height: 20px;
 }
 
 .pImg {
@@ -287,10 +330,21 @@
   padding-top: 23px !important;
   padding-bottom: 23px !important;
   margin-bottom: 10px;
+  .coomposer {
+    padding-top: 2px;
+    padding-left: 6px;
+    max-width: 120px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
   .tag {
     flex-shrink: 0;
     // padding: 2px 4px 0;
     border-radius: 4px;
+    & + .tag {
+      margin-left: 5px;
+    }
   }
 
   .info {
@@ -341,6 +395,7 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
+  // height: 45px;
   .priceSection {
     font-size: 14px;
     font-weight: 400;
@@ -358,6 +413,7 @@
 }
 
 .buyBtn {
+  --van-button-default-height: 38px;
   :global {
     .van-button {
       padding: 0 22px;

+ 388 - 97
src/views/music/music-detail/index.tsx

@@ -6,11 +6,22 @@ import {
   reactive,
   ref
 } from 'vue'
+import umiRequest from 'umi-request'
 import { useRoute, useRouter } from 'vue-router'
 import request from '@/helpers/request'
 import ColHeader from '@/components/col-header'
-import { postMessage } from '@/helpers/native-message'
-import { Button, Cell, Dialog, Icon, Image, Popup, Tag } from 'vant'
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
+import {
+  Button,
+  Cell,
+  Dialog,
+  Icon,
+  Image,
+  Popup,
+  Sticky,
+  Tag,
+  Toast
+} from 'vant'
 import styles from './index.module.less'
 // import Item from '../list/item'
 import { useRect } from '@vant/use'
@@ -22,12 +33,22 @@ import ColSticky from '@/components/col-sticky'
 import { moneyFormat } from '@/helpers/utils'
 import { orderStatus } from '@/views/order-detail/orderStatus'
 import iconShare from '@/views/music/album/icon_share.svg'
+import iconAlbum from '@/views/music/component/images/icon_album.png'
 import iconDownload from './images/icon_download.png'
 import AstronautJSON from './animate/bigLoad.json'
 import ColShare from '@/components/col-share'
 import iconCollect from './images/icon_collect.png'
 import iconCollectActive from './images/icon_collect_active.png'
 import iconListen from './images/icon_listen.png'
+import iconTeacher from '@common/images/icon_teacher.png'
+import {
+  addMusicTitle,
+  addWatermark,
+  convasToImg,
+  imgToCanvas
+} from './imageFunction'
+import Plyr from 'plyr'
+import 'plyr/dist/plyr.css'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `../component/images/${fileName}`
@@ -41,17 +62,16 @@ export default defineComponent({
     localStorage.setItem('behaviorId', getRandomKey())
     const router = useRouter()
     const route = useRoute()
-    const albumDetail = ref<any>(null)
     const loading = ref(false)
     const aId = Number(route.query.activityId) || 0
     const studentActivityId = ref(aId)
     const isError = ref(false)
-    const favorited = ref(0)
-    const albumFavoriteCount = ref(0)
     const headers = ref(null)
+    const footers = ref(null)
     const heightInfo = ref<any>('0')
     const musicDetail = ref<any>(null)
     const showImg = ref<string>('')
+    const accompanyUrl = ref<string>('')
 
     const colors: any = {
       FREE: {
@@ -81,56 +101,250 @@ export default defineComponent({
         })
         musicDetail.value = res.data
         showImg.value = res.data.musicImg || ''
+
+        if (!showImg.value) {
+          setAccompanyUrl()
+          window.addEventListener(
+            'message',
+            async e => {
+              // 给图片设置背景色
+              const tempCanvas = await imgToCanvas(e.data)
+              const img = convasToImg(tempCanvas)
+              //  开始上传图片
+              uploadFunction(img)
+            },
+            false
+          )
+        }
       } catch (error) {
         isError.value = true
       }
       loading.value = false
     }
+    const base64ToBlob = data => {
+      const arr = data.split(','),
+        mime = arr[0].match(/:(.*?);/)[1]
+
+      const bstr = atob(arr[1])
+      let n = bstr.length
+      const u8arr = new Uint8Array(n)
+
+      while (n--) {
+        u8arr[n] = bstr.charCodeAt(n)
+      }
+      return new Blob([u8arr], { type: mime })
+    }
+    const uploadFunction = async file => {
+      try {
+        const formData = new FormData()
+        const fileName =
+          new Date().getTime() +
+          musicDetail.value?.musicSheetName.replaceAll(' ', '_') +
+          '.png'
+        const keyTime = new Date().getTime() + fileName
+        const obj = {
+          filename: fileName,
+          bucketName: 'cloud-coach',
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: keyTime,
+            unknowValueField: []
+          }
+        }
+
+        const res = await request.post(state.platformApi + '/getUploadSign', {
+          data: obj
+        })
+
+        Toast.loading({
+          message: '加载中...',
+          forbidClick: true,
+          loadingType: 'spinner',
+          duration: 0
+        })
+
+        const dataObj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: keyTime,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+        for (const key in dataObj) {
+          formData.append(key, dataObj[key])
+        }
+        const files = base64ToBlob(file)
+
+        formData.append('file', files, fileName)
+        const ossUploadUrl = 'https://ks3-cn-beijing.ksyuncs.com/cloud-coach'
+        await umiRequest(ossUploadUrl, {
+          method: 'POST',
+          data: formData
+        })
+        Toast.clear()
+        const imgurl = ossUploadUrl + '/' + keyTime
+
+        await request.post(state.platformApi + '/open/music/sheet/img', {
+          data: { musicSheetId: musicDetail.value.id, musicImg: imgurl }
+        })
+        showImg.value = imgurl
+      } catch (e) {
+        console.log(e)
+      }
+    }
+
+    const setAccompanyUrl = () => {
+      const url = 'http://dev.colexiu.com'
+      const music = musicDetail.value
+      let subjectId = ''
+      if (music.background && music.background.length > 0) {
+        subjectId = music.background[0].id
+      }
+      accompanyUrl.value =
+        url +
+        `/accompany/colxiu-website.html?id=${music.id}&part-index=${subjectId}`
+    }
+    const player = ref<any>(null)
+    const audio = ref<any>(null)
+    const freeRate = ref<any>(0)
+    const initAudio = async () => {
+      const config = await request.get(
+        '/api-student/sysConfig/queryByParamNameList',
+        {
+          params: {
+            paramNames: 'music_sheet_free_rate'
+          }
+        }
+      )
+      freeRate.value = config.data[0]?.paramValue || 0
 
-    const favoriteLoading = ref(false)
+      const controls = [
+        // 'play-large',
+        'play',
+        'progress',
+        // 'captions',
+        // 'fullscreen',
+        'duration'
+      ]
+      player.value = new Plyr(audio.value, {
+        controls: controls
+      })
 
-    onMounted(() => {
-      FetchList()
+      player.value.on('timeupdate', () => {
+        // 允许播放时间
+        const players = player.value
+        const playTime = (players.duration * freeRate.value) / 100 || 0
+        // 时间,是否购买,是否免费
+        if (
+          players.currentTime >= playTime &&
+          musicDetail.value?.orderStatus !== 'PAID' &&
+          !paymentType.value.includes('FREE')
+        ) {
+          // players.stop()
+          players.pause()
+        }
+      })
+    }
+    onMounted(async () => {
+      await FetchList()
       const { height } = useRect(headers as any)
-      heightInfo.value = height
+      const footer = useRect(footers as any)
+      heightInfo.value = height + footer.height
+
+      // 初始化音频
+      if (musicDetail.value?.audioFileUrl) {
+        initAudio()
+      }
     })
 
-    const toggleFavorite = async (id: number) => {
-      favoriteLoading.value = true
+    const toggleFavorite = async () => {
       try {
-        await request.post('/music/album/favorite/' + id, {
+        await request.post('/music/sheet/favorite/' + musicDetail.value?.id, {
           prefix:
             state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
         })
-        favorited.value = favorited.value === 1 ? 0 : 1
-        albumFavoriteCount.value += favorited.value ? 1 : -1
-      } catch (error) {}
-      favoriteLoading.value = false
+        musicDetail.value.favorite = musicDetail.value?.favorite ? 0 : 1
+        musicDetail.value.favoriteCount = musicDetail.value?.favorite
+          ? musicDetail.value.favoriteCount + 1
+          : musicDetail.value.favoriteCount - 1 < 0
+          ? 0
+          : musicDetail.value.favoriteCount - 1
+        setTimeout(() => {
+          Toast(musicDetail.value?.favorite ? '收藏成功' : '取消收藏成功')
+        }, 100)
+      } catch (error) {
+        //
+      }
+    }
+
+    const saveLoading = ref<boolean>(false)
+    const image = ref('')
+    const onSaveImg = async () => {
+      // 判断是否在保存中...
+      if (saveLoading.value) {
+        return
+      }
+      saveLoading.value = true
+      // 判断是否已经生成图片
+      if (image.value) {
+        saveImg()
+      } else {
+        const tempCanvas = await imgToCanvas(showImg.value)
+        const titleCanvas = addMusicTitle(tempCanvas, {
+          title: musicDetail.value?.musicSheetName,
+          size: 18
+        })
+        const canvas = await addWatermark(titleCanvas, '酷乐秀')
+        image.value = convasToImg(canvas)
+        await saveImg()
+      }
+    }
+
+    const saveImg = async () => {
+      Toast.loading({
+        message: '图片生成中...',
+        forbidClick: true
+      })
+      setTimeout(() => {
+        saveLoading.value = false
+      }, 100)
+      const res = await promisefiyPostMessage({
+        api: 'savePicture',
+        content: {
+          base64: image.value
+        }
+      })
+      if (res?.content?.status === 'success') {
+        Toast.success('保存成功')
+      } else {
+        Toast.fail('保存失败')
+      }
     }
 
     const onBuy = async () => {
-      const album = albumDetail.value
-      orderStatus.orderObject.orderType = 'ALBUM'
-      orderStatus.orderObject.orderName = album.albumName
-      orderStatus.orderObject.orderDesc = album.albumName
-      orderStatus.orderObject.actualPrice = album.albumPrice
+      const music = musicDetail.value
+      orderStatus.orderObject.orderType = 'MUSIC'
+      orderStatus.orderObject.orderName = music.musicSheetName
+      orderStatus.orderObject.orderDesc = music.musicSheetName
+      orderStatus.orderObject.actualPrice = music.musicPrice
       orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
       orderStatus.orderObject.activityId = route.query.activityId || 0
       orderStatus.orderObject.orderNo = ''
       orderStatus.orderObject.orderList = [
         {
-          orderType: 'ALBUM',
-          goodsName: album.albumName,
-          recomUserId: route.query.recomUserId || 0,
-          price: album.albumPrice,
-          ...album
+          orderType: 'MUSIC',
+          goodsName: music.musicSheetName,
+          actualPrice: music.musicPrice,
+          ...music
         }
       ]
 
       const res = await request.post('/api-student/userOrder/getPendingOrder', {
         data: {
-          goodType: 'ALBUM',
-          bizId: album.id
+          goodType: 'MUSIC',
+          bizId: music.id
         }
       })
 
@@ -159,12 +373,12 @@ export default defineComponent({
       }
     }
     const routerTo = () => {
-      const album = albumDetail.value
+      const music = musicDetail.value
       router.push({
         path: '/orderDetail',
         query: {
-          orderType: 'ALBUM',
-          album: album.id
+          orderType: 'MUSIC',
+          musicId: music.id
         }
       })
     }
@@ -201,12 +415,13 @@ export default defineComponent({
         })
         let url =
           location.origin +
-          `/accompany/colexiu-share.html?id=${musicDetail.value?.id}&recomUserId=${state.user.data?.userId}&userType=${state.platformType}`
+          `/teacher.html#/shareMusic?id=${musicDetail.value?.id}&recomUserId=${state.user.data?.userId}&userType=${state.platformType}`
         // 判断是否有活动
         if (res.data.discount === 1) {
           url += `&activityId=${res.data.activityId}`
         }
         shareDiscount.value = res.data.discount || 0
+        console.log(url)
         shareUrl.value = url
         shareStatus.value = true
         return
@@ -215,35 +430,40 @@ export default defineComponent({
     return () => {
       return (
         <div class={styles.detail}>
-          <ColHeader
-            background="transparent"
-            border={false}
-            color="#fff"
-            backIconColor="white"
-            v-slots={{
-              right: () => (
-                <div
-                  class={styles.shareBtn}
-                  style={{
-                    color: '#fff'
-                  }}
-                  onClick={onShare}
-                >
-                  <Image src={iconShare} />
-                  分享
-                </div>
-              )
-            }}
-          />
-          <img class={styles.bgImg} src={musicDetail.value?.titleImg} />
+          <Sticky position="top">
+            <div ref={headers}>
+              <ColHeader
+                background="transparent"
+                border={false}
+                isFixed={false}
+                color="#fff"
+                title={musicDetail.value?.musicSheetName}
+                backIconColor="white"
+                v-slots={{
+                  right: () => (
+                    <div
+                      class={styles.shareBtn}
+                      style={{
+                        color: '#fff'
+                      }}
+                      onClick={onShare}
+                    >
+                      <Image src={iconShare} />
+                      分享
+                    </div>
+                  )
+                }}
+              />
+            </div>
+          </Sticky>
 
+          <img class={styles.bgImg} src={musicDetail.value?.titleImg} />
+          <div class={styles.bgContent}></div>
           <div
             class={styles.musicContainer}
             style={{
               marginTop: '16px',
-              height: `calc(100vh - var(--van-nav-bar-height) - ${
-                heightInfo.value + 16 + 'px'
-              })`
+              height: `calc(100vh - ${heightInfo.value + 16 + 'px'})`
             }}
           >
             <Cell
@@ -290,14 +510,14 @@ export default defineComponent({
                           src={getAssetsHomeFile('icon_album_active.png')}
                         />
                       )}
-                      <span style={{ paddingTop: '2px', paddingLeft: '6px' }}>
+                      <span class={styles.coomposer}>
                         {musicDetail.value?.composer}
                       </span>
                     </p>
                   </div>
                 ),
                 value: () => (
-                  <span class={styles.download}>
+                  <span class={styles.download} onClick={() => onSaveImg()}>
                     <img src={iconDownload} />
                     下载曲谱
                   </span>
@@ -309,7 +529,7 @@ export default defineComponent({
                 id="containerPrint"
                 ref="print"
                 style="width: 100%;page-break-after:always; height: 0"
-                // src={state.accompanyUrl}
+                src={accompanyUrl.value}
               />
               <p class={styles.musicTitle}>
                 {musicDetail.value?.musicSheetName}
@@ -327,65 +547,136 @@ export default defineComponent({
               )}
 
               <div class={styles.videoOperation}>
-                <div class={[styles.audition]}>
-                  <img src={iconListen} />
-                  <span>每首曲目可试听30%</span>
-                </div>
+                {musicDetail.value?.audioFileUrl && (
+                  <>
+                    {(paymentType.value.includes('CHARGE') ||
+                      paymentType.value.includes('VIP')) &&
+                      musicDetail.value?.orderStatus !== 'PAID' && (
+                        <div class={[styles.audition]}>
+                          <img src={iconListen} />
+                          <span>每首曲目可试听{freeRate.value}%</span>
+                        </div>
+                      )}
+
+                    <div class={[styles.audio, styles.collectCell]}>
+                      <audio id="player" controls ref={audio}>
+                        <source
+                          src={musicDetail.value?.audioFileUrl}
+                          type="audio/mp3"
+                        />
+                      </audio>
+                    </div>
+                  </>
+                )}
 
                 <div class={[styles.collect, styles.collectCell]}>
                   <div class={[styles.userInfo]}>
-                    <img src="" />
-                    <span>用户名</span>
+                    <img src={musicDetail.value?.userAvatar || iconTeacher} />
+                    <span>{musicDetail.value?.userName}</span>
                   </div>
 
-                  <div class={[styles.collectSection]}>
-                    <span>326人收藏</span>
-                    <img src={iconCollect} />
+                  <div
+                    class={[styles.collectSection]}
+                    onClick={() => toggleFavorite()}
+                  >
+                    <span>{musicDetail.value?.favoriteCount}人收藏</span>
+                    <img
+                      src={
+                        musicDetail.value?.favorite
+                          ? iconCollectActive
+                          : iconCollect
+                      }
+                    />
                   </div>
                 </div>
               </div>
             </div>
 
-            <div class={[styles.lookAlbum, styles.collectCell]}>
-              <div class="">进入曲目所在专辑列表</div>
-              <Icon name="arrow" />
+            <div
+              class={[styles.lookAlbum, styles.collectCell]}
+              onClick={() => {
+                router.push({
+                  path: '/look-album-list',
+                  query: {
+                    id: musicDetail.value?.id
+                  }
+                })
+              }}
+            >
+              <div>
+                <img src={iconAlbum} />
+                <span>进入曲目所在专辑列表</span>
+              </div>
+              <Icon name="arrow" size={16} color="#666" />
             </div>
           </div>
 
-          <ColSticky position="bottom" background="white">
-            <div ref={headers}>
-              <div class={styles.colSticky}>
-                <div class={styles.priceSection}>
-                  <span>点播价:</span>
-                  <span class={styles.price}>
-                    <i>¥</i>
-                    {moneyFormat(9.9)}
-                  </span>
-                </div>
-                <div class={[styles.buyBtn]}>
+          {musicDetail.value?.id && (
+            <ColSticky position="bottom" background="white">
+              <div ref={footers}>
+                {/* 判断是否是免费的,或者已经购买过 */}
+                {paymentType.value.includes('FREE') ||
+                musicDetail.value?.orderStatus === 'PAID' ? (
                   <Button
                     round
+                    block
                     type="primary"
                     color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
-                    class={styles.primary}
-                    onClick={onBuy}
+                    onClick={() => musicBuy(musicDetail.value)}
                   >
-                    立即点播
+                    立即练习
                   </Button>
+                ) : (
+                  <div class={styles.colSticky}>
+                    <div class={styles.priceSection}>
+                      <span>点播价:</span>
+                      <span class={styles.price}>
+                        <i>¥</i>
+                        {moneyFormat(musicDetail.value?.musicPrice)}
+                      </span>
+                    </div>
+                    <div class={[styles.buyBtn]}>
+                      {/* 判断是否是需要收费的 */}
+                      {paymentType.value.includes('CHARGE') && (
+                        <Button
+                          round
+                          type="primary"
+                          color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
+                          class={styles.primary}
+                          onClick={onBuy}
+                        >
+                          立即点播
+                        </Button>
+                      )}
 
-                  <Button
-                    round
-                    type="primary"
-                    color="linear-gradient(180deg, #F7BD8D 0%, #CD8806 100%)"
-                    class={styles.memeber}
-                    onClick={onBuy}
-                  >
-                    开通会员
-                  </Button>
-                </div>
+                      {/* 判断是否有会员的 */}
+                      {paymentType.value.includes('VIP') && (
+                        <Button
+                          round
+                          type="primary"
+                          color="linear-gradient(180deg, #F7BD8D 0%, #CD8806 100%)"
+                          class={styles.memeber}
+                          onClick={() => {
+                            router.push({
+                              path: '/memberCenter',
+                              query: {
+                                ...route.query
+                              }
+                            })
+                          }}
+                        >
+                          {studentActivityId.value > 0 && (
+                            <div class={[styles.buttonDiscount]}>专属优惠</div>
+                          )}
+                          开通会员
+                        </Button>
+                      )}
+                    </div>
+                  </div>
+                )}
               </div>
-            </div>
-          </ColSticky>
+            </ColSticky>
+          )}
 
           <Popup
             v-model:show={shareStatus.value}

+ 8 - 4
src/views/music/personal/collection.tsx

@@ -1,10 +1,8 @@
 import { defineComponent, reactive, ref } from 'vue'
 import { List } from 'vant'
 import request from '@/helpers/request'
-import Item from '../list/item'
-import { useRoute } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import ColResult from '@/components/col-result'
-import { musicBuy } from '../music'
 import { state } from '@/state'
 import styles from './index.module.less'
 import Song from '../component/song'
@@ -14,6 +12,7 @@ export default defineComponent({
   emits: ['favorite'],
   setup(props, { expose, emit }) {
     const route = useRoute()
+    const router = useRouter()
     const params = reactive({
       search: (route.query.search as string) || '',
       musicTagIds: route.query.tagids || '',
@@ -73,7 +72,12 @@ export default defineComponent({
             <Song
               list={rows.value}
               onDetail={(item: any) => {
-                musicBuy(item)
+                router.push({
+                  path: '/music-detail',
+                  query: {
+                    id: item.id
+                  }
+                })
               }}
             />
           </div>

+ 8 - 4
src/views/music/personal/personal.tsx

@@ -1,9 +1,7 @@
 import { defineComponent, reactive, ref } from 'vue'
 import { List } from 'vant'
 import request from '@/helpers/request'
-import Item from '../list/item'
-import { musicBuy } from '../music'
-import { useRoute } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import ColResult from '@/components/col-result'
 import { state } from '@/state'
 import styles from './index.module.less'
@@ -14,6 +12,7 @@ export default defineComponent({
   emits: ['favorite'],
   setup(props, { expose, emit }) {
     const route = useRoute()
+    const router = useRouter
     const params = reactive({
       search: (route.query.search as string) || '',
       musicTagIds: route.query.tagids || '',
@@ -73,7 +72,12 @@ export default defineComponent({
             <Song
               list={rows.value}
               onDetail={(item: any) => {
-                musicBuy(item)
+                router.push({
+                  path: '/music-detail',
+                  query: {
+                    id: item.id
+                  }
+                })
               }}
             />
           </div>

+ 8 - 19
src/views/music/personal/practice.tsx

@@ -3,15 +3,16 @@ import { useAsyncState } from '@vueuse/core'
 import { defineComponent, ref } from 'vue'
 import { Cell, Skeleton } from 'vant'
 import Item from '../list/item'
-import { musicBuy } from '../music'
 import { state } from '@/state'
 import styles from './index.module.less'
 import Song from '../component/song'
+import { useRouter } from 'vue-router'
 
 export default defineComponent({
   name: 'Practice',
   emits: ['favorite'],
   setup(props, { expose, emit }) {
+    const router = useRouter()
     /** 这里条数不会变动,设置固定高度避免抖动 */
     const prevNum = ref(0)
     const {
@@ -46,27 +47,15 @@ export default defineComponent({
             <Song
               list={list}
               onDetail={(item: any) => {
-                musicBuy(item)
+                router.push({
+                  path: '/music-detail',
+                  query: {
+                    id: item.id
+                  }
+                })
               }}
             />
           </div>
-          {/* {Array.from(Array(prevNum.value)).map((_, index) => {
-            const item = list[index]
-            console.log(list, item)
-            if (!item) {
-              return <Skeleton row={5} />
-            }
-            return (
-              <Item
-                key={item.id}
-                data={item}
-                onClick={() => musicBuy(item)}
-                onFavorite={() => {
-                  emit('favorite')
-                }}
-              />
-            )
-          })} */}
         </>
       )
     }

+ 1 - 1
src/views/music/search/header.tsx

@@ -12,7 +12,7 @@ import { useLocalStorage } from '@vueuse/core'
 import styles from './index.module.less'
 import classNames from 'classnames'
 import SelectTag from './select-tag'
-import { getRandomKey, musicBuy } from '../music'
+import { getRandomKey } from '../music'
 import SelectSubject from './select-subject'
 import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
 import { state } from '@/state'

+ 7 - 8
src/views/music/search/index.tsx

@@ -4,7 +4,7 @@ import AlbumList from '../album'
 import MusicList from '../list'
 import styles from './index.module.less'
 import { useRoute, useRouter } from 'vue-router'
-import { getRandomKey, musicBuy } from '../music'
+import { getRandomKey } from '../music'
 import { mitter } from './header'
 import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
 
@@ -93,13 +93,12 @@ export default defineComponent({
               hideSearch
               ref={musicList}
               onItemClick={(item: any) => {
-                musicBuy(item, path => {
-                  router.push({
-                    path,
-                    query: {
-                      orderType: 'MUSIC'
-                    }
-                  })
+                router.push({
+                  path: '/music-detail',
+                  query: {
+                    id: item.id,
+                    albumId: route.params.id
+                  }
                 })
               }}
               defauleParams={{

+ 5 - 8
src/views/music/songbook/list.tsx

@@ -4,7 +4,6 @@ import request from '@/helpers/request'
 // import { useAsyncState } from '@vueuse/core'
 // import Item from '../list/item'
 import styles from './index.module.less'
-import { musicBuy } from '../music'
 import { useRoute, useRouter } from 'vue-router'
 import ColResult from '@/components/col-result'
 // import { state as tempState } from '@/state'
@@ -102,13 +101,11 @@ export default defineComponent({
               <Song
                 list={data.value.rows}
                 onDetail={(item: any) => {
-                  musicBuy(item, (path: any) => {
-                    router.push({
-                      path,
-                      query: {
-                        orderType: 'MUSIC'
-                      }
-                    })
+                  router.push({
+                    path: '/music-detail',
+                    query: {
+                      id: item.id
+                    }
                   })
                 }}
               />

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

@@ -34,7 +34,7 @@ export default defineComponent({
             </div>
           </div>
           <div class={styles.buttons}>
-            <Button
+            {/* <Button
               class={classNames(
                 styles.btn,
                 styles[item.chargeType.toLocaleLowerCase()]
@@ -43,7 +43,7 @@ export default defineComponent({
             >
               {chargeTypes[item.chargeType]}
               <Icon name="arrow" />
-            </Button>
+            </Button> */}
           </div>
         </header>
         <footer class={styles.footer}>