Browse Source

添加上传曲谱功能

lex 9 months ago
parent
commit
c80a5f829c

BIN
src/common/images/icon-delete.png


BIN
src/common/images/icon_uploader.png


+ 72 - 65
src/components/col-upload/index.module.less

@@ -1,65 +1,72 @@
-.uploader-section {
-  margin: 10px 0;
-  height: 145px;
-  border: 1px dashed #ccc;
-  border-radius: 10px;
-  box-sizing: border-box;
-  position: relative;
-  .img-close {
-    position: absolute;
-    top: 8px;
-    right: 10px;
-    z-index: 99;
-    font-size: 16px;
-    background-color: #333;
-    color: #fff;
-    width: 22px;
-    height: 22px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    border-radius: 50%;
-  }
-  .col-uploader {
-    width: 100%;
-    height: 100%;
-    align-items: center;
-    display: flex;
-    justify-content: center;
-  }
-  :global {
-    .van-uploader {
-      width: 100%;
-      height: 100%;
-      align-items: center;
-      display: flex;
-      justify-content: center;
-    }
-    .van-uploader__wrapper, .van-uploader__input-wrapper {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: inherit;
-      height: inherit;
-    }
-  }
-  .uploader {
-    // width: 300px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    flex-direction: column;
-    .uploaderText {
-      font-size: 14px;
-      color: #999999;
-      margin-top: 8px;
-    }
-  }
-
-  .uploadImg {
-    width: 100%;
-    height: 100%;
-    // border-radius: 10px;
-    overflow: hidden;
-  }
-}
+.uploader-section {
+  margin: 10px 0;
+  height: 145px;
+  border: 1px dashed #ccc;
+  border-radius: 10px;
+  box-sizing: border-box;
+  position: relative;
+
+  .img-close {
+    position: absolute;
+    top: 4px;
+    right: 4px;
+    z-index: 99;
+    font-size: 16px;
+    // background-color: #333;
+    color: #fff;
+    width: 20px;
+    height: 20px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: 50%;
+  }
+
+  .col-uploader {
+    width: 100%;
+    height: 100%;
+    align-items: center;
+    display: flex;
+    justify-content: center;
+  }
+
+  :global {
+    .van-uploader {
+      width: 100%;
+      height: 100%;
+      align-items: center;
+      display: flex;
+      justify-content: center;
+    }
+
+    .van-uploader__wrapper,
+    .van-uploader__input-wrapper {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: inherit;
+      height: inherit;
+    }
+  }
+
+  .uploader {
+    // width: 300px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+
+    .uploaderText {
+      font-size: 14px;
+      color: #999999;
+      margin-top: 8px;
+    }
+  }
+
+  .uploadImg {
+    width: 100%;
+    height: 100%;
+    // border-radius: 10px;
+    overflow: hidden;
+  }
+}

+ 2 - 13
src/components/col-upload/index.tsx

@@ -6,6 +6,7 @@ import { useCustomFieldValue } from '@vant/use'
 import { postMessage } from '@/helpers/native-message'
 import umiRequest from 'umi-request'
 import iconUploader from '@common/images/icon_uploader.png'
+import iconDelete from '@common/images/icon-delete.png'
 import request from '@/helpers/request'
 import { getOssUploadUrl, state } from '@/state'
 import { getUploadSign, onOnlyFileUpload } from '@/helpers/oss-file-upload'
@@ -148,18 +149,6 @@ export default defineComponent({
           name: key,
           file: file
         }
-        console.log(obj, 'obj')
-        // const formData = new FormData()
-        // for (const key in obj) {
-        //   formData.append(key, obj[key])
-        // }
-        // formData.append('file', file, fileName)
-        // await umiRequest(getOssUploadUrl(this.bucket), {
-        //   method: 'POST',
-        //   data: formData
-        // })
-        // console.log(getOssUploadUrl(this.bucket) + key)
-        // const uploadUrl = getOssUploadUrl(this.bucket) + key
         const uploadUrl = await onOnlyFileUpload(
           getOssUploadUrl(this.bucket),
           obj
@@ -178,7 +167,7 @@ export default defineComponent({
       <div class={styles['uploader-section']}>
         {this.modelValue && this.deletable ? (
           <Icon
-            name="cross"
+            name={iconDelete}
             onClick={this.onClose}
             class={styles['img-close']}
           />

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

@@ -339,6 +339,13 @@ export default [
         meta: {
           title: '教程播放'
         }
+      },
+      {
+        path: '/upload-protocol',
+        component: () => import('@/teacher/music/upload-protocol'),
+        meta: {
+          title: '协议'
+        }
       }
     ]
   },

+ 19 - 0
src/teacher/music/upload-protocol/index.module.less

@@ -0,0 +1,19 @@
+ .mProtocol {
+   :global {
+     iframe {
+       // visibility: hidden;
+       border: none;
+       width: 100%;
+       height: 100vh;
+
+       body {
+         ::-webkit-scrollbar-thumb {
+           background-color: #efeff0;
+           border: 1px solid transparent;
+           background-clip: padding-box;
+           border-radius: 5px;
+         }
+       }
+     }
+   }
+ }

+ 41 - 0
src/teacher/music/upload-protocol/index.tsx

@@ -0,0 +1,41 @@
+import ColHeader from '@/components/col-header'
+import styles from './index.module.less'
+import { defineComponent } from 'vue'
+
+// 预览协议 - 原生实名认证使用
+export default defineComponent({
+  name: 'preview-protocol',
+  data() {
+    console.log(this.$route.query)
+    return {
+      protocolUrl: '' as any,
+      title: this.$route.query.type === 'music' ? '曲谱排版规范' : '常见问题'
+    }
+  },
+  async mounted() {
+    const type = this.$route.query.type
+    if (type === 'question') {
+      this.protocolUrl = `${location.origin}/teacher/muic-standard/question.html`
+    } else if (type === 'music') {
+      this.protocolUrl = `${location.origin}/teacher/muic-standard/index.html`
+      this.title = '曲谱排版规范'
+    } else {
+      this.protocolUrl = `${location.origin}/teacher/muic-standard/index.html`
+    }
+  },
+  render() {
+    return (
+      <div id="mProtocol" class={styles.mProtocol}>
+        <ColHeader title={this.title} />
+
+        <iframe
+          id="staffIframeRef"
+          // style={{
+          //   opacity: loading.value ? 0 : 1
+          // }}
+          src={this.protocolUrl}
+        ></iframe>
+      </div>
+    )
+  }
+})

BIN
src/teacher/music/upload/images/banner-bg.png


BIN
src/teacher/music/upload/images/icon-delete.png


BIN
src/teacher/music/upload/images/icon-title-update.png


BIN
src/teacher/music/upload/images/icon-title-upload.png


+ 42 - 2
src/teacher/music/upload/index.module.less

@@ -21,9 +21,17 @@
   cursor: pointer;
 }
 
+.titleImg {
+  position: absolute;
+  top: -58px;
+  left: 17px;
+  width: 129px;
+  height: 31px;
+}
+
 .area {
   position: relative;
-  padding: 44px 0 12px;
+  padding: 34px 0 12px;
   margin: 22px 14px 12px;
   border-radius: 16px;
 
@@ -42,6 +50,7 @@
     background-size: contain;
 
     &.section-title2 {
+      top: -8px;
       background: url('./images/title-bg2.png') no-repeat center;
       background-size: contain;
     }
@@ -183,6 +192,23 @@
   }
 }
 
+.btnSection {
+  position: relative;
+  width: 100%;
+
+  .iconDelete {
+    cursor: pointer;
+    position: absolute;
+    right: -2px;
+    top: -2px;
+    display: inline-block;
+    width: 18px;
+    height: 18px;
+    background: url('./images/icon-delete.png') no-repeat center;
+    background-size: contain;
+  }
+}
+
 .clear-px {
   padding-left: 0;
   padding-right: 0;
@@ -200,9 +226,23 @@
   }
 }
 
+.tagMore {
+  :global {
+    .van-field__control {
+      visibility: hidden;
+      opacity: 0;
+    }
+  }
+}
 
 .showField {
+  padding-top: 0 !important;
+
   :global {
+    .van-field__label {
+      display: none;
+    }
+
     .van-field__control {
       display: flex;
       flex-wrap: wrap;
@@ -285,7 +325,7 @@
 .tags {
   margin-right: 5px;
   margin-bottom: 5px;
-  margin-top: 5px;
+  // margin-top: 5px;
   background: #F2FFFC;
   border-radius: 4px;
   height: 28px;

+ 279 - 153
src/teacher/music/upload/index.tsx

@@ -2,48 +2,29 @@ import { defineComponent } from 'vue'
 import {
   Button,
   Field,
-  Sticky,
   Form,
   Tag,
   Radio,
   RadioGroup,
   Popup,
-  Icon,
   Empty,
-  Picker,
   Toast,
-  NoticeBar,
-  CellGroup,
-  Cell,
-  Dialog
+  CellGroup
 } from 'vant'
-import ColFieldGroup from '@/components/col-field-group'
-// import { MusicType } from 'src/teacher/music/list/item.d'
-import SubjectModel from './subjectModal'
-import InstrumentModal from './instrumentModal'
-import ColField from '@/components/col-field'
-
-import {
-  teachercanEvaluateType,
-  teacherChargeType,
-  teachershowAudiType,
-  teachershowFingeringType,
-  teachershowHasBeatType,
-  teacherNotationType,
-  teacherStyleType,
-  teacherExquisiteType,
-  teacherPaymentType
-} from '@/constant/music'
+import { state as appState } from '@/state'
+import { teachershowAudiType, teacherPaymentType } from '@/constant/music'
 import { getXmlInfo, FormatXMLInfo } from '@/helpers/music-xml'
 import Upload from './upload'
 import styles from './index.module.less'
-import SelectTag from '@/views/music/search/select-tag'
+
 import { browser } from '@/helpers/utils'
 import { postMessage } from '@/helpers/native-message'
 import request from '@/helpers/request'
 import requestOrigin from 'umi-request'
 import UploadIcon from './images/music-icon.png'
 import btnBg from './images/btn-bg.png'
+import iconTitleUpload from './images/icon-title-upload.png'
+import iconTitleUpdate from './images/icon-title-update.png'
 import ColUpload from '@/components/col-upload'
 import {
   verifiyNumberInteger,
@@ -52,6 +33,7 @@ import {
 import ColHeader from '@/components/col-header'
 import ColSticky from '@/components/col-sticky'
 import MessageTip from './message-tip'
+import SelectTag from './select-tag'
 
 export type BackgroundMp3 = {
   url?: string
@@ -87,7 +69,7 @@ export default defineComponent({
       composer: '',
       remark: '',
       // repeatedBeats: 0,
-      playSpeed: null as any,
+      playSpeed: '100' as any,
       // hasBeat: 0,
       musicCover: '',
       paymentType: 'CHARGE',
@@ -101,7 +83,7 @@ export default defineComponent({
       selectTagVisible: false,
       subJectVisible: false,
       instrumentVisible: false,
-      tags: [] as string[],
+      tags: [] as any[],
       tagsNames: [] as Array<{ [id in string]: string }>,
       formated: {} as FormatXMLInfo,
       tagVisibility: false,
@@ -119,7 +101,8 @@ export default defineComponent({
       messageTipStatus: false,
       messageTipTitle: '上传须知',
       messageTipType: 'upload' as 'upload' | 'error' | 'origin',
-      cbsInstrumentList: [] as any
+      cbsInstrumentList: [] as any,
+      tagList: [] as any
     }
   },
   watch: {
@@ -146,6 +129,18 @@ export default defineComponent({
         })
       })
 
+    const prefix =
+      appState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
+
+    request(prefix + '/MusicTag/tree').then((res: any) => {
+      console.log(res, 'res')
+      this.tagList = res.data || []
+    })
+
+    if (this.$route.params.id) {
+      this.setDetail(this.$route.params.id as string)
+    }
+
     request
       .post('/api-teacher/musicalInstrument/list')
       .then((response: any) => {
@@ -161,7 +156,56 @@ export default defineComponent({
       })
   },
   methods: {
-    onComfirm(tags: any, names: any) {
+    async setDetail(id: string) {
+      try {
+        const { data } = await request.get(
+          `/api-teacher/music/sheet/detail/${id}`
+        )
+
+        this.playMode = data.audioType || 'MP3'
+        this.xmlFileUrl = data.xmlFileUrl
+        this.name = data.musicSheetName
+        this.composer = data.composer
+        this.playSpeed = data.playSpeed
+        // this.tags = data.musicTag?.split(',')
+        const names = data.musicTagNames.split(',')
+        this.tags = data.musicTag.split(',')
+        this.tags = this.tags
+          .filter((el: any) => {
+            return el != ''
+          })
+          .map(e => Number(e))
+        for (let i = 0; i < names.length; i++) {
+          this.tagsNames[this.tags[i]] = names[i]
+        }
+
+        this.musicCover = data.titleImg
+        this.midiFileUrl = data.midiUrl
+        this.mp3Url = data.metronomeUrl
+        this.remark = data.remark
+        this.paymentType = data.paymentType
+        this.musicPrice = data.musicPrice || 0
+        // this.extConfigJson = data.extConfigJson
+        this.backgroundMp3s = data.background.map((item: any) => {
+          return {
+            url: item.audioFileUrl,
+            trackName: item.musicalInstrumentName,
+            id: item.musicalInstrumentId,
+            track: item.track,
+            loading: false
+          }
+        })
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    onComfirm(tags: any) {
+      const names: any = []
+      this.tagList.forEach(tag => {
+        if (tags.includes(tag.id)) {
+          names[tag.id] = tag.name
+        }
+      })
       this.tagsNames = names
       this.tagVisibility = false
       const data = Object.values(tags).flat().filter(Boolean) as string[]
@@ -192,8 +236,10 @@ export default defineComponent({
             console.log(currentItem, 'currentItem')
             tempMp3s.push({
               url: '',
+              id: currentItem.id,
               trackName: currentItem.name,
-              track: currentItem.code
+              track: currentItem.code,
+              loading: currentItem.loading
             })
           }
         }
@@ -241,6 +287,8 @@ export default defineComponent({
 
               let resultIndexStatus = false
               const partNames = formated.partNames || []
+
+              const tempMp3s: BackgroundMp3[] = []
               for (const i of partNames) {
                 const index = this.cbsInstrumentList.findIndex(
                   cbs => cbs.code?.indexOf(i) > -1
@@ -249,7 +297,20 @@ export default defineComponent({
                   resultIndexStatus = true
                   break
                 }
+
+                const currentItem = this.cbsInstrumentList[index]
+                if (currentItem) {
+                  console.log(currentItem, 'currentItem')
+                  tempMp3s.push({
+                    url: '',
+                    id: currentItem.id,
+                    trackName: currentItem.name,
+                    track: currentItem.code,
+                    loading: currentItem.loading
+                  })
+                }
               }
+
               if (partNames.length <= 0 || resultIndexStatus) {
                 this.messageTipStatus = true
                 this.messageTipTitle = '解析失败'
@@ -257,7 +318,9 @@ export default defineComponent({
                 this.xmlFileUrl = ''
                 return
               }
+
               this.formated = formated
+              this.backgroundMp3s = tempMp3s
             })
           }
         }
@@ -293,7 +356,8 @@ export default defineComponent({
         { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
         evt => {
           // @ts-ignore
-          this.bgmp3Url = evt?.fileUrl || this.bgmp3Url || ''
+          this.backgroundMp3s[index].url =
+            evt?.fileUrl || this.backgroundMp3s[index].url || ''
           this.backgroundMp3s[index].loading = false
         }
       )
@@ -316,33 +380,41 @@ export default defineComponent({
     },
     createSubmitData() {
       return {
-        playMode: this.playMode, // 播放模式
-        xmlFileUrl: this.xmlFileUrl, // XML
-        name: this.name, // 曲目名称
-        composer: this.composer, // 音乐人
-        playSpeed: this.playSpeed, // 曲目速度
-        musicCover: this.musicCover, // 曲目封面
-
-        multiTracksSelection: '1001', // 声轨名
-        midiFileUrl: this.midiFileUrl, // MID文件
-        musicSheetAccompanimentList: [
-          {
-            audioFileUrl: this.mp3Url,
-            sortNumber: 1,
-            audioPlayType: 'PLAY'
-          }
-        ], // 伴奏
-        // audioType: 'HOMEMODE', // HOMEMODE  默认自制
-        musicSheetSoundList: this.backgroundMp3s.map(item => ({
-          // audioFileUrl: this.bgmp3Url,
-          // track: item.track
-          musicalInstrumentId: item.id,
-          musicalInstrumentName: item.trackName,
-          audioFileUrl: item.url,
-          audioPlayType: 'PLAY' // SING
-        })), // 原音
-        musicalInstrumentIds: '1001', // 乐器编号
-        extConfigJson: '{"repeatedBeats":0,"gradualTimes":{},"isEvxml":0}'
+        musicSheetJson: {
+          playMode: this.playMode, // 播放模式
+          xmlFileUrl: this.xmlFileUrl, // XML
+          name: this.name, // 曲目名称
+          composer: this.composer, // 音乐人
+          playSpeed: this.playSpeed, // 曲目速度
+          remark: this.remark,
+          musicTagIds: this.tags?.join(','),
+          musicCover: this.musicCover, // 曲目封面
+          multiTracksSelection: this.backgroundMp3s
+            .map(item => item.track)
+            ?.join(','), // 声轨名
+          midiFileUrl: this.midiFileUrl, // MID文件
+          musicSheetAccompanimentList: [
+            {
+              audioFileUrl: this.mp3Url,
+              sortNumber: 1,
+              audioPlayType: 'PLAY'
+            }
+          ], // 伴奏
+          musicPrice: this.musicPrice,
+          paymentType: this.paymentType,
+          // audioType: 'HOMEMODE', // HOMEMODE  默认自制
+          musicSheetSoundList: this.backgroundMp3s.map(item => ({
+            musicalInstrumentId: item.id,
+            musicalInstrumentName: item.trackName,
+            track: item.track,
+            audioFileUrl: item.url,
+            audioPlayType: 'PLAY' // SING
+          })), // 原音
+          musicalInstrumentIds: this.backgroundMp3s
+            .map(item => item.id)
+            ?.join(','), // 乐器编号
+          extConfigJson: '{"repeatedBeats":0,"gradualTimes":{},"isEvxml":0}'
+        }
       }
     },
     async onSubmit(vals: any) {
@@ -350,46 +422,52 @@ export default defineComponent({
       this.submitLoading = true
       try {
         if (this.$route.params.id) {
-          await request.post('/api-teacher/music/sheet/update', {
+          await request.post('/api-teacher/musicSheetAuthRecord/update', {
             data: {
               ...this.createSubmitData(),
               id: this.$route.params.id
             }
           })
         } else {
-          await request.post('/api-teacher/music/sheet/save', {
+          await request.post('/api-teacher/musicSheetAuthRecord/save', {
             data: this.createSubmitData()
           })
         }
+        Toast('上传成功')
+        setTimeout(() => {
+          postMessage({
+            api: 'back'
+          })
+        }, 800)
       } catch (error) {
         //
+      } finally {
+        setTimeout(() => {
+          this.submitLoading = false
+        }, 800)
       }
 
-      Toast('上传成功')
-      setTimeout(() => {
-        postMessage({
-          api: 'back'
-        })
-        this.submitLoading = false
-      }, 800)
       console.log(vals)
     },
-    failed() {
-      console.log('failed', this.backgroundMp3s)
+    onFailed(e: any) {
+      console.log('failed', e)
     }
   },
   render() {
-    // console.log(this.formated)
-    // const browserInfo = browser()
     return (
       <Form
         class={styles.form}
         onSubmit={this.onSubmit}
         onFailed={this.onFailed}
       >
-        <ColHeader hideHeader={false} background="transparent" border={false} />
+        <ColHeader
+          title=" "
+          hideHeader={false}
+          background="transparent"
+          border={false}
+        />
 
-        <CellGroup class={[styles.area, styles.topArea]}>
+        <CellGroup class={[styles.area, styles.topArea]} border={false}>
           <div
             class={styles.uploadMessage}
             onClick={() => {
@@ -401,6 +479,11 @@ export default defineComponent({
             上传须知
           </div>
 
+          <img
+            class={styles.titleImg}
+            src={this.$route.params.id ? iconTitleUpdate : iconTitleUpload}
+          />
+
           <div class={styles['section-title']}></div>
           <Field required label="播放类型" center inputAlign="right">
             {{
@@ -444,16 +527,30 @@ export default defineComponent({
                 ),
                 input: () =>
                   browser().isApp ? (
-                    <Button
-                      icon={UploadIcon}
-                      class={styles.upbtn}
-                      loading={this.mp3Loading}
-                      onClick={this.naiveMp3File}
-                    >
-                      {this.mp3Url
-                        ? this.fileName(this.mp3Url)
-                        : '上传伴奏文件'}
-                    </Button>
+                    <div class={styles.btnSection}>
+                      <Button
+                        icon={UploadIcon}
+                        class={styles.upbtn}
+                        loading={this.mp3Loading}
+                        onClick={() => {
+                          if (this.mp3Url) return
+                          this.naiveMp3File()
+                        }}
+                      >
+                        {this.mp3Url
+                          ? this.fileName(this.mp3Url)
+                          : '上传伴奏文件'}
+                      </Button>
+
+                      {this.mp3Url && (
+                        <i
+                          class={styles.iconDelete}
+                          onClick={() => {
+                            this.mp3Url = ''
+                          }}
+                        ></i>
+                      )}
+                    </div>
                   ) : (
                     <>
                       <Upload
@@ -487,16 +584,30 @@ export default defineComponent({
                 ),
                 input: () =>
                   browser().isApp ? (
-                    <Button
-                      icon={UploadIcon}
-                      class={styles.upbtn}
-                      loading={this.mp3Loading}
-                      onClick={this.naiveMidFile}
-                    >
-                      {this.midiFileUrl
-                        ? this.fileName(this.midiFileUrl)
-                        : '上传MIDI文件'}
-                    </Button>
+                    <div class={styles.btnSection}>
+                      <Button
+                        icon={UploadIcon}
+                        class={styles.upbtn}
+                        loading={this.mp3Loading}
+                        onClick={() => {
+                          if (this.midiFileUrl) return
+                          this.naiveMidFile()
+                        }}
+                      >
+                        {this.midiFileUrl
+                          ? this.fileName(this.midiFileUrl)
+                          : '上传MIDI文件'}
+                      </Button>
+
+                      {this.midiFileUrl && (
+                        <i
+                          class={styles.iconDelete}
+                          onClick={() => {
+                            this.midiFileUrl = ''
+                          }}
+                        ></i>
+                      )}
+                    </div>
                   ) : (
                     <>
                       <Upload
@@ -532,16 +643,31 @@ export default defineComponent({
               ),
               input: () =>
                 browser().isApp ? (
-                  <Button
-                    icon={UploadIcon}
-                    class={styles.upbtn}
-                    loading={this.xmlFileLoading}
-                    onClick={this.naiveXMLFile}
-                  >
-                    {this.xmlFileUrl
-                      ? this.fileName(this.xmlFileUrl)
-                      : '上传XML文件'}
-                  </Button>
+                  <div class={styles.btnSection}>
+                    <Button
+                      icon={UploadIcon}
+                      class={styles.upbtn}
+                      loading={this.xmlFileLoading}
+                      onClick={() => {
+                        if (this.xmlFileUrl) return
+                        this.naiveXMLFile()
+                      }}
+                    >
+                      {this.xmlFileUrl
+                        ? this.fileName(this.xmlFileUrl)
+                        : '上传XML文件'}
+                    </Button>
+
+                    {this.xmlFileUrl && (
+                      <i
+                        class={styles.iconDelete}
+                        onClick={() => {
+                          this.xmlFileUrl = ''
+                          this.backgroundMp3s = []
+                        }}
+                      ></i>
+                    )}
+                  </div>
                 ) : (
                   <>
                     <Upload
@@ -599,14 +725,28 @@ export default defineComponent({
                 ),
                 input: () =>
                   browser().isApp ? (
-                    <Button
-                      icon={UploadIcon}
-                      class={styles.upbtn}
-                      loading={mp3.loading}
-                      onClick={() => this.naiveBGMp3File(index)}
-                    >
-                      {mp3.url ? this.fileName(mp3.url) : '上传原声文件'}
-                    </Button>
+                    <div class={styles.btnSection}>
+                      <Button
+                        icon={UploadIcon}
+                        class={styles.upbtn}
+                        loading={mp3.loading}
+                        onClick={() => {
+                          if (mp3.url) return
+                          this.naiveBGMp3File(index)
+                        }}
+                      >
+                        {mp3.url ? this.fileName(mp3.url) : '上传原声文件'}
+                      </Button>
+
+                      {mp3.url && (
+                        <i
+                          class={styles.iconDelete}
+                          onClick={() => {
+                            mp3.url = ''
+                          }}
+                        ></i>
+                      )}
+                    </div>
                   ) : (
                     <>
                       <Upload
@@ -623,7 +763,7 @@ export default defineComponent({
           ))}
         </CellGroup>
 
-        <CellGroup class={[styles.area]}>
+        <CellGroup class={[styles.area]} border={false}>
           <div
             class={[styles['section-title'], styles['section-title2']]}
           ></div>
@@ -637,6 +777,8 @@ export default defineComponent({
             errorMessageAlign="right"
             placeholder="请输入曲目名称"
             inputAlign="right"
+            autocomplete="off"
+            maxlength={20}
             onUpdate:modelValue={val => (this.name = val)}
           />
 
@@ -649,6 +791,8 @@ export default defineComponent({
             errorMessageAlign="right"
             placeholder="请输入音乐人"
             inputAlign="right"
+            autocomplete="off"
+            maxlength={14}
             onUpdate:modelValue={val => (this.composer = val)}
           />
 
@@ -676,6 +820,7 @@ export default defineComponent({
                 <ColUpload
                   cropper
                   bucket="cloud-coach"
+                  tips="上传封面"
                   options={{
                     autoCropWidth: 600,
                     autoCropHeight: 600
@@ -696,8 +841,9 @@ export default defineComponent({
             rules={[{ required: true, message: '请输入曲目速度' }]}
             errorMessageAlign="right"
             v-model={this.playSpeed}
-            class={styles.inputControl}
+            // class={styles.inputControl}
             formatter={this.onFormatter2}
+            autocomplete="off"
           ></Field>
 
           <Field
@@ -707,26 +853,30 @@ export default defineComponent({
             isLink
             required
             readonly
+            border={this.tags.length > 0 ? false : true}
+            name="tags"
+            modelValue={this.tags?.join(',')}
+            class={this.tags.length > 0 ? styles.tagMore : ''}
+            rules={[{ required: true, message: '请选择曲目标签' }]}
+            errorMessageAlign="right"
             onClick={() => {
               this.tagVisibility = true
             }}
           ></Field>
           {this.tags.length > 0 && (
-            <Field
-              name="tags"
-              class={styles.showField}
-              modelValue={this.tags.length ? 1 : undefined}
-              rules={[{ required: true, message: '请选择曲目标签' }]}
-              // @ts-ignore
-              vSlots={{
+            <Field class={styles.showField}>
+              {{
                 input: () =>
                   this.tags.length > 0 ? (
-                    this.tags.map((item: any) => (
+                    this.tags.map((item: any, index: number) => (
                       <Tag
                         type="primary"
                         size="large"
                         class={styles.tags}
                         closeable
+                        onClose={() => {
+                          this.tags.splice(index, 1)
+                        }}
                       >
                         {this.tagsNames[item]}
                       </Tag>
@@ -739,35 +889,9 @@ export default defineComponent({
                     />
                   )
               }}
-            />
+            </Field>
           )}
 
-          {/* <Field required label="是否精品" center inputAlign="right">
-            {{
-              input: () => (
-                <RadioGroup
-                  class={styles['radio-group']}
-                  modelValue={this.exquisiteFlag}
-                  onUpdate:modelValue={val => {
-                    this.exquisiteFlag = Number(val)
-                  }}
-                >
-                  {Object.keys(teacherExquisiteType).map((item: string) => {
-                    const isActive = item === String(this.exquisiteFlag)
-                    const type = isActive ? 'primary' : 'default'
-                    return (
-                      <Radio class={styles.radio} name={item}>
-                        <Tag size="large" plain={isActive} type={type}>
-                          {teacherExquisiteType[item]}
-                        </Tag>
-                      </Radio>
-                    )
-                  })}
-                </RadioGroup>
-              )
-            }}
-          </Field> */}
-
           <Field required label="是否收费" center inputAlign="right">
             {{
               input: () => (
@@ -802,9 +926,11 @@ export default defineComponent({
                 class={styles.inputControl}
                 placeholder=" "
                 formatter={this.onFormatter}
+                autocomplete="off"
                 v-slots={{ button: () => '元' }}
                 modelValue={this.musicPrice}
                 maxlength={8}
+                center
                 rules={[
                   { required: true, validator, message: '请输入收费价格' }
                 ]}
@@ -842,7 +968,7 @@ export default defineComponent({
               block
               round
               native-type="submit"
-              loading={this.submitLoading}
+              disabled={this.submitLoading}
             >
               <img src={btnBg} />
             </Button>
@@ -883,12 +1009,12 @@ export default defineComponent({
         >
           <SelectTag
             onConfirm={this.onComfirm}
-            onCancel={() => {
+            show={this.tagVisibility}
+            onClose={() => {
               this.tagVisibility = false
             }}
-            rowSingle
-            defaultValue={this.tags.join(',')}
-            needAllButton={false}
+            tagList={this.tagList}
+            defaultValue={this.tags}
           />
         </Popup>
 

+ 7 - 0
src/teacher/music/upload/message-tip/index.module.less

@@ -15,6 +15,13 @@
     width: 100px;
     height: 60px;
   }
+
+  &.dialogError {
+    &::before {
+      background: url('../images/error-icon.png') no-repeat top center;
+      background-size: contain;
+    }
+  }
 }
 
 .popupContainer {

+ 14 - 5
src/teacher/music/upload/message-tip/index.tsx

@@ -59,9 +59,11 @@ export default defineComponent({
       let url = `${location.origin}/teacher/#/registerProtocol`
 
       if (type === 'question') {
-        url = `${location.origin}/teacher/muic-standard/question.html`
+        // url = `${location.origin}/teacher/muic-standard/question.html`
+        url = `${location.origin}/teacher.html#/upload-protocol?type=question`
       } else if (type === 'music') {
-        url = `${location.origin}/teacher/muic-standard/index.html`
+        // url = `${location.origin}/teacher/muic-standard/index.html`
+        url = `${location.origin}/teacher.html#/upload-protocol?type=music`
       }
 
       postMessage({
@@ -80,7 +82,10 @@ export default defineComponent({
           round
           style={{ width: '88%' }}
           closeOnClickOverlay={false}
-          class={styles.wxPopupDialog}
+          class={[
+            styles.wxPopupDialog,
+            props.type === 'error' ? styles.dialogError : ''
+          ]}
         >
           <div class={styles.popupContainer}>
             <p class={styles.title1}>
@@ -102,7 +107,11 @@ export default defineComponent({
                   </div>
                   <p class={styles.cTitle}>曲谱审核标准:</p>
                   <div class={styles.cContent}>
-                    1、文件大小不要超过5MB,不符合版面规范的乐谱,审核未通过的不予上架,详情参考《曲谱排版规范》;
+                    1、文件大小不要超过5MB,不符合版面规范的乐谱,审核未通过的不予上架,详情参考
+                    <span onClick={() => onDetail('music')}>
+                      《曲谱排版规范》
+                    </span>
+                    ;
                     <br />
                     2、XML与MIDI文件内容必须一致,推荐使用Sibelius打谱软件;导出设置:导出XML-未压缩(*.XML)/导出MIDI:音色-其他回放设备General
                     MIDI、MIDI、MIDI文件类型-类型0、不要勾选“将弱拍小节导出为具有休止符的完整小节”。点击查看
@@ -117,7 +126,7 @@ export default defineComponent({
                 <div class={styles.container}>
                   <div class={styles.cContent}>
                     声轨名称解析失败,请对照
-                    <span onClick={() => onDetail('protocol')}>
+                    <span onClick={() => onDetail('music')}>
                       《曲谱排版规范》
                     </span>
                     检查后重试

+ 181 - 0
src/teacher/music/upload/select-tag/index.module.less

@@ -0,0 +1,181 @@
+.title {
+  font-size: 18px;
+  font-weight: bold;
+  color: black;
+  padding-top: 19px;
+  text-align: center;
+}
+
+.tit {
+  color: #333;
+  line-height: 22px;
+  font-size: 16px;
+  margin: 20px 0;
+  margin-top: 10px;
+}
+
+.childContent {
+  display: flex;
+  flex-wrap: wrap;
+  text-align: center;
+
+  .item {
+    display: block;
+    margin-right: 5px;
+    margin-bottom: 10px;
+    min-width: 80px;
+    height: 32px;
+
+    &:nth-child(4n + 0) {
+      margin-right: 0;
+    }
+  }
+}
+
+.radio-group {
+  display: flex;
+  margin-top: 14px;
+
+  .radio:first-child {
+    :global {
+      .van-radio__label {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+.radio {
+  box-sizing: border-box;
+
+  :global {
+
+    .van-radio__icon,
+    .van-checkbox__icon {
+      display: none;
+    }
+
+    .van-checkbox__label {
+      margin-left: 0;
+    }
+
+    .van-tag--large {
+      // width: 94px;
+      // height: 30px;
+      font-size: 14px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .van-tag {
+      box-sizing: border-box;
+    }
+  }
+}
+
+.btn {
+  width: 164px;
+}
+
+.select {
+  padding: 0 16px;
+
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  >div {
+    flex: 1;
+    overflow: hidden;
+    overflow-y: auto;
+  }
+
+  >footer {
+    padding: 10px 0;
+    display: flex;
+    justify-content: space-between;
+  }
+}
+
+.title {
+  font-size: 18px;
+  font-weight: bold;
+  color: black;
+  padding-top: 19px;
+  text-align: center;
+}
+
+.tit {
+  color: #333;
+  line-height: 22px;
+  font-size: 16px;
+  margin: 20px 0;
+  margin-top: 10px;
+}
+
+.childContent {
+  display: flex;
+  flex-wrap: wrap;
+  text-align: center;
+
+  .item {
+    display: block;
+    margin-right: 5px;
+    margin-bottom: 10px;
+    min-width: 80px;
+    height: 32px;
+
+    &:nth-child(4n + 0) {
+      margin-right: 0;
+    }
+  }
+}
+
+.radio-group {
+  display: flex;
+  margin-top: 14px;
+
+  .radio:first-child {
+    :global {
+      .van-radio__label {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+.radio {
+  box-sizing: border-box;
+
+  :global {
+
+    .van-radio__icon,
+    .van-checkbox__icon {
+      display: none;
+    }
+
+    .van-checkbox__label {
+      margin-left: 0;
+    }
+
+    .van-tag--large {
+      // width: 94px;
+      // height: 30px;
+      font-size: 14px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .van-tag {
+      box-sizing: border-box;
+    }
+  }
+}
+
+.btn {
+  width: 164px;
+}

+ 115 - 0
src/teacher/music/upload/select-tag/index.tsx

@@ -0,0 +1,115 @@
+import { Ref, defineComponent, ref, watch } from 'vue'
+import {
+  Tag,
+  CheckboxGroup,
+  Checkbox,
+  RadioGroup,
+  Radio,
+  Button,
+  Toast
+} from 'vant'
+import styles from './index.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'SelectTag',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    tagList: {
+      type: Array,
+      default: () => []
+    },
+    defaultValue: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { attrs, emit }) {
+    const tagList: Ref<Array<any | number>> = ref([...props.defaultValue])
+
+    const onSelect = val => {
+      setTimeout(() => {
+        val.forEach(i => {
+          if (!tagList.value.includes(i)) {
+            tagList.value.push(i)
+          }
+        })
+      }, 100)
+    }
+
+    watch(
+      () => props.show,
+      () => {
+        if (!props.show) {
+          resetTags()
+        } else {
+          tagList.value = [...props.defaultValue]
+        }
+      }
+    )
+
+    const resetTags = () => {
+      tagList.value = [...props.defaultValue]
+      emit('close')
+    }
+    return () => {
+      return (
+        <div class={styles.select}>
+          <h4 class={styles.title}>全部声部</h4>
+          <div class={styles.content}>
+            <CheckboxGroup
+              class={classNames(styles.childContent, styles['radio-group'])}
+              modelValue={tagList.value}
+              max={3}
+              onUpdate:modelValue={val => {
+                onSelect!(val)
+              }}
+            >
+              {props.tagList.map((item: any) => (
+                <Checkbox
+                  key={item.id}
+                  name={item.id}
+                  class={styles.radio}
+                  onClick={() => {
+                    if (tagList.value.length >= 3) {
+                      Toast('最多只能选3个标签')
+                      return
+                    }
+                    onSelect!([item.id])
+                  }}
+                >
+                  <Tag
+                    class={classNames(styles.item, 'van-ellipsis')}
+                    plain={!tagList.value.includes(item.id)}
+                    type="primary"
+                    round
+                    size="large"
+                  >
+                    {item.name}
+                  </Tag>
+                </Checkbox>
+              ))}
+            </CheckboxGroup>
+          </div>
+          <footer class="van-safe-area-bottom van-hairline--top">
+            <Button class={styles.btn} round onClick={resetTags}>
+              重置
+            </Button>
+            <Button
+              class={styles.btn}
+              type="primary"
+              round
+              onClick={() => emit('confirm', tagList.value)}
+            >
+              确认
+            </Button>
+          </footer>
+        </div>
+      )
+    }
+  }
+})

+ 17 - 16
src/tenant/music/music-detail/new-index.module.less

@@ -46,8 +46,8 @@
   margin-right: 16px !important;
   position: relative;
 
-  > img,
-  > div {
+  >img,
+  >div {
     position: absolute;
     border-radius: 10px;
     overflow: hidden;
@@ -255,7 +255,7 @@
     margin-top: -4px;
   }
 
-  & > span {
+  &>span {
     display: flex;
     align-items: center;
     padding-top: 1px;
@@ -313,11 +313,9 @@
     bottom: 0;
     left: 0;
     right: 0;
-    background: linear-gradient(
-      180deg,
-      rgba(255, 255, 255, 0) 0%,
-      #ffffff 100%
-    );
+    background: linear-gradient(180deg,
+        rgba(255, 255, 255, 0) 0%,
+        #ffffff 100%);
     height: 287px;
   }
 
@@ -359,6 +357,10 @@
   .musicImg {
     width: 100%;
     min-height: 50vh;
+
+    img {
+      width: 100%;
+    }
   }
 
   .finch {
@@ -431,13 +433,12 @@
       font-weight: 500;
       color: #ffffff;
       line-height: 25px;
+
       &.van-button--disabled {
         opacity: initial;
-        background: linear-gradient(
-          270deg,
-          #ff7a93 0%,
-          #ff9daa 100%
-        ) !important;
+        background: linear-gradient(270deg,
+            #ff7a93 0%,
+            #ff9daa 100%) !important;
       }
     }
   }
@@ -480,7 +481,7 @@
       // width: 48%;
       width: 100%;
 
-      & + .van-button {
+      &+.van-button {
         margin-left: 12px;
       }
     }
@@ -624,14 +625,14 @@
     margin-right: 14px;
     word-break: break-all;
 
-    > h4 {
+    >h4 {
       color: var(--music-list-item-title-color);
       font-size: 14px;
       font-weight: 600;
       width: 200px;
     }
 
-    > p {
+    >p {
       color: var(--music-list-item-mate-color);
       line-height: 17px;
       padding-top: 3px;