Forráskód Böngészése

Merge branch 'iteration-20240618'

lex 9 hónapja
szülő
commit
1738dae25e
34 módosított fájl, 3592 hozzáadás és 930 törlés
  1. BIN
      src/common/images/icon-delete.png
  2. BIN
      src/common/images/icon_uploader.png
  3. 72 65
      src/components/col-upload/index.module.less
  4. 2 13
      src/components/col-upload/index.tsx
  5. 6 2
      src/constant/music.ts
  6. 7 0
      src/router/routes-teacher.ts
  7. 19 0
      src/teacher/music/upload-protocol/index.module.less
  8. 41 0
      src/teacher/music/upload-protocol/index.tsx
  9. BIN
      src/teacher/music/upload/images/banner-bg.png
  10. BIN
      src/teacher/music/upload/images/bell.png
  11. BIN
      src/teacher/music/upload/images/btn-bg.png
  12. BIN
      src/teacher/music/upload/images/error-icon.png
  13. BIN
      src/teacher/music/upload/images/icon-delete.png
  14. BIN
      src/teacher/music/upload/images/icon-question.png
  15. BIN
      src/teacher/music/upload/images/icon-title-update.png
  16. BIN
      src/teacher/music/upload/images/icon-title-upload.png
  17. BIN
      src/teacher/music/upload/images/message-top-bg.png
  18. BIN
      src/teacher/music/upload/images/music-icon.png
  19. BIN
      src/teacher/music/upload/images/title-bg.png
  20. BIN
      src/teacher/music/upload/images/title-bg2.png
  21. BIN
      src/teacher/music/upload/images/upload-icon.png
  22. 1076 0
      src/teacher/music/upload/index copy.tsx
  23. 145 0
      src/teacher/music/upload/index.module copy.less
  24. 284 17
      src/teacher/music/upload/index.module.less
  25. 775 817
      src/teacher/music/upload/index.tsx
  26. 181 0
      src/teacher/music/upload/instrumentModal/index.module.less
  27. 108 0
      src/teacher/music/upload/instrumentModal/index.tsx
  28. 113 0
      src/teacher/music/upload/message-tip/index.module.less
  29. 166 0
      src/teacher/music/upload/message-tip/index.tsx
  30. 181 0
      src/teacher/music/upload/select-tag/index.module.less
  31. 115 0
      src/teacher/music/upload/select-tag/index.tsx
  32. 181 0
      src/teacher/music/upload/subjectModal/index.module.less
  33. 103 0
      src/teacher/music/upload/subjectModal/index.tsx
  34. 17 16
      src/tenant/music/music-detail/new-index.module.less

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']}
           />

+ 6 - 2
src/constant/music.ts

@@ -17,6 +17,10 @@ export const teacherChargeType = {
   2: '是',
   0: '否'
 }
+export const teacherPaymentType = {
+  CHARGE: '是',
+  FREE: '否',
+}
 
 /** 是否支持简谱 */
 export const teacherNotationType = {
@@ -37,8 +41,8 @@ export const teachershowFingeringType = {
 
 /** 老师端展示播放类型 */
 export const teachershowAudiType = {
-  MIDI: 'MIDI',
-  MP3: 'MP3'
+  MP3: 'MP3',
+  MIDI: 'MIDI'
 }
 
 /** 老师端展示是否包含节拍器类型 */

+ 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/bell.png


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


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


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


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


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


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


BIN
src/teacher/music/upload/images/message-top-bg.png


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


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


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


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


+ 1076 - 0
src/teacher/music/upload/index copy.tsx

@@ -0,0 +1,1076 @@
+import { defineComponent } from 'vue'
+import {
+  Button,
+  Field,
+  Sticky,
+  Form,
+  Tag,
+  Radio,
+  RadioGroup,
+  Popup,
+  Icon,
+  Empty,
+  Picker,
+  Toast,
+  NoticeBar
+} from 'vant'
+import ColFieldGroup from '@/components/col-field-group'
+// import { MusicType } from 'src/teacher/music/list/item.d'
+import SubjectModel from '@/business-components/subject-list'
+import ColField from '@/components/col-field'
+
+import {
+  teachercanEvaluateType,
+  teacherChargeType,
+  teachershowAudiType,
+  teachershowFingeringType,
+  teachershowHasBeatType,
+  teacherNotationType,
+  teacherStyleType,
+  teacherExquisiteType
+} 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 { teacherState } from '@/teacher/teacher-cert/teacherState'
+import request from '@/helpers/request'
+import requestOrigin from 'umi-request'
+import UploadIcon from './upload.svg'
+import ColUpload from '@/components/col-upload'
+import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
+import { state } from '@/state'
+import ColHeader from '@/components/col-header'
+
+export type BackgroundMp3 = {
+  url?: string
+  track?: string
+}
+
+// 校验函数返回 true 表示校验通过,false 表示不通过
+export const validator = val => {
+  console.log(val)
+  if (Number(val) <= 0) {
+    return '收费金额必须大于0'
+  } else {
+    return true
+  }
+}
+
+export default defineComponent({
+  name: 'MusicUpload',
+  data() {
+    return {
+      reason: '',
+      audioType: 'MP3',
+      xmlFileUrl: '',
+      xmlFileLoading: false,
+      midiUrl: '',
+      midiLoading: false,
+      mp3Url: '',
+      bgmp3Url: '',
+      mp3Loading: false,
+      bgmp3Loading: false,
+      musicSheetName: '',
+      composer: '',
+      speed: '',
+      hasBeat: 0,
+      titleImg: '',
+      accompanimentType: 'HOMEMODE',
+      chargeType: 0,
+      paymentType: '',
+      showFingering: 1,
+      canEvaluate: 1,
+      notation: 1,
+      musicPrice: '',
+      subJectIndex: 0,
+      selectTagVisible: false,
+      subJectVisible: false,
+      tags: [] as string[],
+      tagsNames: [] as Array<{ [id in string]: string }>,
+      formated: {} as FormatXMLInfo,
+      tagVisibility: false,
+      subjectListres: [] as any[],
+      subjectListNames: {} as any,
+      selectedSubjectList: null as any,
+      vlewSubjectList: null as any,
+      submitLoading: false,
+      showPicker: false,
+      music_sheet_service_fee: 0,
+      music_account_period: 0,
+      exquisiteFlag: 0,
+      backgroundMp3s: [
+        {
+          url: '',
+          track: ''
+        }
+      ] as BackgroundMp3[],
+      checked: false
+    }
+  },
+  watch: {
+    formated() {
+      this.mergeXmlData(this.formated)
+    },
+    chargeType() {
+      if (this.chargeType === 0) {
+        this.musicPrice = ''
+        this.paymentType = ''
+      }
+    }
+  },
+  computed: {
+    choiceSubjectIds() {
+      // 选择的科目编号
+      let ids = teacherState.teacherCert.subjectId
+        ? teacherState.teacherCert.subjectId.split(',')
+        : []
+      ids = ids.map((item: any) => Number(item))
+      return ids
+    },
+    subjectList() {
+      // 学科列表
+      const subjects: any = this.subjectListres || []
+      return subjects
+    },
+    choiceSubject() {
+      // 选择的科目
+      const tempArr: any[] = []
+      this.subjectList.forEach((parent: any) => {
+        parent.subjects &&
+          parent.subjects.forEach((sub: any) => {
+            if (this.choiceSubjectIds.includes(sub.id)) {
+              tempArr.push(sub as never)
+            }
+          })
+      })
+      return tempArr
+    }
+  },
+  async mounted() {
+    // 获取基础数据
+    request
+      .get('/api-teacher/sysConfig/queryByParamNameList', {
+        params: {
+          paramNames: 'music_sheet_service_fee,music_account_period'
+        }
+      })
+      .then((res: any) => {
+        console.log(res, 'res')
+        const data = res.data || []
+        data.forEach((item: any) => {
+          if (item.paramName === 'music_sheet_service_fee') {
+            this.music_sheet_service_fee = item.paramValue
+          } else if (item.paramName === 'music_account_period') {
+            this.music_account_period = item.paramValue
+          }
+        })
+      })
+
+    // console.log(config, 'config')
+    // request
+    //   .get('/api-teacher/sysConfig/queryByParamName', {
+    //     params: {
+    //       paramName: 'music_sheet_service_fee'
+    //     }
+    //   })
+    //   .then(res => (this.music_sheet_service_fee = res.data.paramValue))
+    // if (teacherState.subjectList.length <= 0) {
+    await request.get('/api-teacher/subject/subjectSelect').then(res => {
+      const list: any[] = []
+      for (const item of res.data || []) {
+        const slist: any[] = (item as any).subjects || []
+        list.push(...slist)
+      }
+      this.subjectListres = list
+      this.subjectListNames = this.getSubjectListNames(list)
+    })
+
+    if (this.$route.params.id) {
+      this.setDetail(this.$route.params.id as string)
+    }
+
+    const resVersion = await request.post('/api-teacher/open/appVersion', {
+      data: {
+        platform:
+          state.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher',
+        version: state.version
+      }
+    })
+    this.checked = resVersion.data.check ? true : false
+    // 审核版本金额默认为0
+    if (this.checked) {
+      this.chargeType = 0
+    }
+    // }
+  },
+  methods: {
+    async setDetail(id: string) {
+      try {
+        const res = await request.get('/api-teacher/music/sheet/detail/' + id)
+        this.chargeType = res.data.paymentType === 'FREE' ? 0 : 2
+        this.paymentType = res.data.paymentType
+        this.showFingering = res.data.showFingering
+        this.canEvaluate = res.data.canEvaluate
+        if (this.chargeType) {
+          this.musicPrice = res.data.musicPrice
+        }
+
+        this.composer = res.data.composer
+        this.musicSheetName = res.data.musicSheetName
+        this.audioType = res.data.audioType
+        this.notation = res.data.notation
+        this.selectedSubjectList = {
+          label: res.data.musicSubject,
+          value: res.data.subjectNames
+        }
+        this.vlewSubjectList = {
+          label: res.data.musicSubject,
+          value: res.data.subjectNames
+        }
+        this.subJectIndex = Object.keys(this.subjectListNames).findIndex(
+          key => key === res.data.musicSubject
+        )
+        const names = res.data.musicTagNames.split(',')
+        this.tags = res.data.musicTag.split(',')
+        this.tags = this.tags.filter((el: any) => {
+          return el != ''
+        })
+
+        for (let i = 0; i < names.length; i++) {
+          this.tagsNames[this.tags[i]] = names[i]
+        }
+        this.exquisiteFlag = res.data.exquisiteFlag
+        this.xmlFileUrl = res.data.xmlFileUrl
+        this.accompanimentType = res.data.accompanimentType
+        this.titleImg = res.data.titleImg
+
+        // this.audioType = res.data.mp3Type
+
+        if (this.audioType === 'MP3') {
+          this.hasBeat =
+            (res.data.audioType === 'MP3' &&
+              res.data.mp3Type === 'MP3_METRONOME') ||
+            res.data.audioType === 'MIDI'
+              ? 1
+              : 0
+          this.mp3Url = res.data.audioFileUrl || res.data.url //res.data.metronomeUrl || res.data.url
+        } else {
+          this.midiUrl = res.data.midiUrl
+        }
+
+        this.backgroundMp3s = (res.data.background || []).map((item, index) => {
+          if (index === 0) {
+            this.bgmp3Url = item.audioFileUrl
+          }
+          return {
+            url: item.audioFileUrl,
+            track: item.track
+          }
+        })
+        this.reason = res.data.reason
+
+        console.log(this.bgmp3Url)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    createSubmitData() {
+      const beatType = this.hasBeat ? 'MP3_METRONOME' : 'MP3'
+      const mp3Type = this.audioType === 'MP3' ? beatType : 'MIDI'
+      return {
+        audioType: this.audioType,
+        sourceType: 'TEACHER',
+        mp3Type,
+        hasBeat: Number(this.hasBeat),
+        accompanimentType: this.accompanimentType,
+        titleImg: this.titleImg,
+        url: this.hasBeat ? '' : this.mp3Url,
+        metronomeUrl: this.hasBeat ? this.mp3Url : '',
+        audioFileUrl: this.mp3Url,
+        showFingering: Number(this.showFingering),
+        musicTag: this.tags.join(','),
+        musicSubject: Number(this.selectedSubjectList?.label) || undefined,
+        musicSheetName: this.musicSheetName,
+        midiUrl: this.midiUrl,
+        notation: Number(this.notation),
+        xmlFileUrl: this.xmlFileUrl,
+        canEvaluate: Number(this.canEvaluate),
+        chargeType: this.chargeType === 0 ? 'FREE' : 'CHARGE',
+        paymentType: this.chargeType === 0 ? 'FREE' : 'CHARGE',
+        exquisiteFlag: this.exquisiteFlag,
+        composer: this.composer,
+        musicPrice: this.chargeType === 0 ? 0 : this.musicPrice, // 当选择免费时,重置金额为0
+        background: this.backgroundMp3s.map(item => ({
+          audioFileUrl: this.bgmp3Url,
+          track: item.track
+          // metronomeUrl: this.hasBeat ? this.bgmp3Url : ''
+        }))
+      }
+    },
+    async submit(vals: any) {
+      console.log(vals)
+      this.submitLoading = true
+      try {
+        if (this.$route.params.id) {
+          await request.post('/api-teacher/music/sheet/update', {
+            data: {
+              ...this.createSubmitData(),
+              id: this.$route.params.id
+            }
+          })
+        } else {
+          await request.post('/api-teacher/music/sheet/create', {
+            data: this.createSubmitData()
+          })
+        }
+      } catch (error) {}
+
+      Toast('上传成功')
+      setTimeout(() => {
+        postMessage({
+          api: 'back'
+        })
+        this.submitLoading = false
+      }, 800)
+      console.log(vals)
+    },
+    onFormatter(val: any) {
+      return verifyNumberIntegerAndFloat(val)
+    },
+    getSubjectListNames(list) {
+      const data = {}
+      for (const item of list) {
+        data[item.id] = item.name
+        if (item.subjects) {
+          for (const sub of item.subjects) {
+            data[sub.id] = sub.name
+          }
+        }
+      }
+      return data
+    },
+    failed() {
+      console.log('failed', this.backgroundMp3s)
+    },
+    mergeXmlData(data: FormatXMLInfo) {
+      this.formated = data
+      // this.backgroundMp3s = data.partNames.map((partName: string) => ({
+      //   track: partName
+      // }))
+      if (!this.musicSheetName) {
+        this.musicSheetName = data.title
+      }
+      if (!this.composer) {
+        this.composer = data.composer
+      }
+      // if (!this.speed && data.speed) {
+      //   this.speed = '' + data.speed
+      // }
+    },
+    readerFile(file: File) {
+      const reader = new FileReader()
+      reader.onload = () => {
+        const xml = reader.result as string
+        this.formated = getXmlInfo(xml)
+      }
+      reader.readAsText(file)
+    },
+    onChoice(val: any) {
+      this.subJectVisible = false
+      this.selectedSubjectList = [val]
+    },
+    onComfirm(tags: any, names: any) {
+      this.tagsNames = names
+      this.tagVisibility = false
+      const data = Object.values(tags).flat().filter(Boolean) as string[]
+      console.log(data)
+      this.tags = data
+    },
+    naiveXMLFile() {
+      this.xmlFileLoading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'xml', bucket: 'cloud-coach' } },
+        evt => {
+          // @ts-ignore
+          this.xmlFileUrl = evt?.fileUrl || this.xmlFileUrl || ''
+          this.xmlFileLoading = false
+          if (this.xmlFileUrl) {
+            requestOrigin(this.xmlFileUrl).then(
+              res => (this.formated = getXmlInfo(res))
+            )
+          }
+        }
+      )
+    },
+    naiveMidFile() {
+      this.midiLoading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'midi', bucket: 'cloud-coach' } },
+        evt => {
+          // @ts-ignore
+          this.midiUrl = evt?.fileUrl || this.midiUrl || ''
+          this.midiLoading = false
+          // this.midiUrl = path
+        }
+      )
+    },
+    naiveMp3File() {
+      this.mp3Loading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
+        evt => {
+          // @ts-ignore
+          this.mp3Url = evt?.fileUrl || this.mp3Url || ''
+          this.mp3Loading = false
+          // this.midiUrl = path
+        }
+      )
+    },
+    naiveBGMp3File() {
+      this.bgmp3Loading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
+        evt => {
+          this.bgmp3Url
+          // @ts-ignore
+          this.bgmp3Url = evt?.fileUrl || this.bgmp3Url || ''
+          this.bgmp3Loading = false
+          // this.midiUrl = path
+        }
+      )
+    },
+    fileName(name = '') {
+      return name.split('/').pop()
+    },
+    removeBackground(index: number) {
+      this.backgroundMp3s.splice(index, 1)
+    },
+    onDetail(type: string) {
+      let url = `${location.origin}/teacher/#/registerProtocol`
+
+      if (type === 'question') {
+        url = `${location.origin}/teacher/muic-standard/question.html`
+      } else if (type === 'music') {
+        url = `${location.origin}/teacher/muic-standard/index.html`
+      }
+
+      postMessage({
+        api: 'openWebView',
+        content: {
+          url,
+          orientation: 1,
+          isHideTitle: false
+        }
+      })
+    }
+  },
+  render() {
+    console.log(this.formated)
+    const browserInfo = browser()
+    return (
+      <Form class={styles.form} onSubmit={this.submit} onFailed={this.failed}>
+        <ColHeader />
+        {this.reason && (
+          <NoticeBar wrapable scrollable={false} text={this.reason} />
+        )}
+        <div class={styles.container}>
+          <div class={styles.tips}>
+            <div class={styles.tipsTitle}>注意事项:</div>
+            <div class={styles.tipsContent}>
+              1、必须是上传人自己参与制作的作品。
+              <br />
+              2、歌曲及歌曲信息中请勿涉及政治、宗教、广告、涉毒、犯罪、色情、低俗、暴力、血腥、消极等违规内容,违反者直接删除内容。多次违反将封号。
+              <br />
+              3、点击查看
+              <span onClick={() => this.onDetail('protocol')}>
+                《用户注册协议》
+              </span>
+              ,如果您上传了文件,即认为您完全同意并遵守该协议的内容;
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField border={false} required title="MusicXML文件">
+              <Field
+                name="xmlFileUrl"
+                modelValue={this.xmlFileUrl}
+                rules={[{ required: true, message: '请选择MusicXML文件' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    browserInfo.isApp ? (
+                      <Button
+                        icon={UploadIcon}
+                        class={styles.upbtn}
+                        onClick={this.naiveXMLFile}
+                        loading={this.xmlFileLoading}
+                      >
+                        {this.xmlFileUrl
+                          ? this.fileName(this.xmlFileUrl)
+                          : '上传文件'}
+                      </Button>
+                    ) : (
+                      <>
+                        <Upload
+                          onUpdate:modelValue={val => (this.xmlFileUrl = val)}
+                          accept=".xml"
+                          formatFile={this.readerFile}
+                        />
+                        <div style={{ marginLeft: '8px' }}>
+                          {this.fileName(this.xmlFileUrl)}
+                        </div>
+                      </>
+                    )
+                }}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsTitle}>曲谱审核标准:</div>
+            <div class={styles.tipsContent}>
+              1、文件大小不要超过5MB,不符合版面规范的乐谱,审核未通过的不予上架,详情参考
+              <span onClick={() => this.onDetail('music')}>
+                《曲谱排版规范》
+              </span>
+              ; 1、必须是上传人自己参与制作的作品。
+              <br />
+              2、XML与MIDI文件内容必须一致,推荐使用Sibelius打谱软件。导出设置:导出XML-未压缩(*.xml)/导出MIDI:音色-其他回放设备General
+              MIDI、MIDI、MIDI文件类型-类型0、不要勾选“将弱拍小节导出为具有休止符的完整小节”。点击查看
+              <span onClick={() => this.onDetail('question')}>
+                《常见问题》
+              </span>
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField required title="播放类型" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.audioType}
+                onUpdate:modelValue={val => (this.audioType = val)}
+              >
+                {Object.keys(teachershowAudiType).map((item: string) => {
+                  const isActive = item === this.audioType
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teachershowAudiType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField>
+            {this.audioType === 'MP3' ? (
+              <>
+                {/* <ColField required title="是否带节拍器" border={false}>
+                  <RadioGroup
+                    class={styles['radio-group']}
+                    modelValue={this.hasBeat}
+                    onUpdate:modelValue={val => (this.hasBeat = val)}
+                  >
+                    {Object.keys(teachershowHasBeatType).map((item: string) => {
+                      const isActive = item === String(this.hasBeat)
+                      const type = isActive ? 'primary' : 'default'
+                      return (
+                        <Radio class={styles.radio} name={item}>
+                          <Tag size="large" plain={isActive} type={type}>
+                            {teachershowHasBeatType[item]}
+                          </Tag>
+                        </Radio>
+                      )
+                    })}
+                  </RadioGroup>
+                </ColField> */}
+                <ColField required title="伴奏类型" border={false}>
+                  <RadioGroup
+                    class={styles['radio-group']}
+                    modelValue={this.accompanimentType}
+                    onUpdate:modelValue={val => (this.accompanimentType = val)}
+                  >
+                    {Object.keys(teacherStyleType).map((item: string) => {
+                      const isActive = item === String(this.accompanimentType)
+                      const type = isActive ? 'primary' : 'default'
+                      return (
+                        <Radio class={styles.radio} name={item}>
+                          <Tag size="large" plain={isActive} type={type}>
+                            {teacherStyleType[item]}
+                          </Tag>
+                        </Radio>
+                      )
+                    })}
+                  </RadioGroup>
+                </ColField>
+                <ColField border={false} title="伴奏文件">
+                  <Field
+                    name="mp3Url"
+                    modelValue={this.mp3Url}
+                    // @ts-ignore
+                    vSlots={{
+                      input: () =>
+                        browserInfo.isApp ? (
+                          <Button
+                            icon={UploadIcon}
+                            class={styles.upbtn}
+                            onClick={this.naiveMp3File}
+                            loading={this.mp3Loading}
+                          >
+                            {this.mp3Url
+                              ? this.fileName(this.mp3Url)
+                              : '上传文件'}
+                          </Button>
+                        ) : (
+                          <>
+                            <Upload
+                              onUpdate:modelValue={val => (this.mp3Url = val)}
+                              accept=".mp3"
+                            />
+                            <div style={{ marginLeft: '8px' }}>
+                              {this.fileName(this.mp3Url)}
+                            </div>
+                          </>
+                        )
+                    }}
+                  />
+                </ColField>
+              </>
+            ) : (
+              <ColField border={false} required title="MIDI文件">
+                <Field
+                  name="midiUrl"
+                  modelValue={this.midiUrl}
+                  rules={[{ required: true, message: '请选择MIDI文件' }]}
+                  // @ts-ignore
+                  vSlots={{
+                    input: () =>
+                      browserInfo.isApp ? (
+                        <Button
+                          icon={UploadIcon}
+                          class={styles.upbtn}
+                          onClick={this.naiveMidFile}
+                          loading={this.midiLoading}
+                        >
+                          {this.midiUrl
+                            ? this.fileName(this.midiUrl)
+                            : '上传文件'}
+                        </Button>
+                      ) : (
+                        <>
+                          <Upload
+                            onUpdate:modelValue={val => (this.midiUrl = val)}
+                            accept=".mid,.midi"
+                          />
+                          <div style={{ marginLeft: '8px' }}>
+                            {this.fileName(this.midiUrl)}
+                          </div>
+                        </>
+                      )
+                  }}
+                />
+              </ColField>
+            )}
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              1、推荐上传自制伴奏,伴奏和谱面必须对齐。自制伴奏可以设置更高的收费标准。
+              <br />
+              2、普通伴奏如果涉及到版权纠纷,根据
+              <span onClick={() => this.onDetail('protocol')}>
+                《用户注册协议》
+              </span>
+              平台有权进行下架处理。
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            {this.audioType === 'MP3' &&
+              this.backgroundMp3s.map((item, index) => (
+                <ColField
+                  required
+                  border={false}
+                  title={(item.track || '') + '原音文件'}
+                  // @ts-ignore
+                  vSlots={{
+                    right: () =>
+                      this.backgroundMp3s.length > 1 ? (
+                        <Button
+                          onClick={() => this.removeBackground(index)}
+                          style={{ border: 'none' }}
+                          icon="cross"
+                        ></Button>
+                      ) : null
+                  }}
+                >
+                  <Field
+                    name="url"
+                    modelValue={this.bgmp3Url}
+                    // @ts-ignore
+                    vSlots={{
+                      input: () =>
+                        browserInfo.isApp ? (
+                          <Button
+                            icon={UploadIcon}
+                            class={styles.upbtn}
+                            onClick={this.naiveBGMp3File}
+                            loading={this.bgmp3Loading}
+                          >
+                            {this.bgmp3Url
+                              ? this.fileName(this.bgmp3Url)
+                              : '上传文件'}
+                          </Button>
+                        ) : (
+                          <>
+                            <Upload
+                              onUpdate:modelValue={val => (this.bgmp3Url = val)}
+                              accept=".mp3"
+                            />
+                            <div style={{ marginLeft: '8px' }}>
+                              {this.fileName(this.bgmp3Url)}
+                            </div>
+                          </>
+                        )
+                    }}
+                  />
+                </ColField>
+              ))}
+            <ColField required title="曲目名称">
+              <Field
+                clearable
+                name="musicSheetName"
+                modelValue={this.musicSheetName}
+                rules={[{ required: true, message: '请输入曲目名称' }]}
+                class={styles['clear-px']}
+                placeholder="请输入曲目名称"
+                onUpdate:modelValue={val => (this.musicSheetName = val)}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              1、同一首曲目不可重复上传,如有不同版本统一用“()”补充。举例:人生的旋转木马(长笛二重奏版)。
+              <br />
+              2、曲目名后可添加曲目信息备注,包含但不限于曲目类型等。曲目名《xxxx》,举例:人生的旋转木马《哈尔的移动城堡》(长笛二重奏版)
+              <br />
+              3、其他信息不要写在曲目名里,如歌手、上传人员昵称等。
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField border={false} required title="曲谱封面">
+              <ColUpload
+                cropper
+                bucket="cloud-coach"
+                options={{
+                  autoCropWidth: 600,
+                  autoCropHeight: 600
+                }}
+                v-model={this.titleImg}
+                class={styles.imgContainer}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <ColFieldGroup class={styles.area}>
+            <ColField required title="艺术家">
+              <Field
+                clearable
+                class={styles['clear-px']}
+                placeholder="请输入艺术家姓名"
+                name="composer"
+                modelValue={this.composer}
+                rules={[{ required: true, message: '请输入艺术家姓名' }]}
+                onUpdate:modelValue={val => (this.composer = val)}
+              />
+            </ColField>
+            {/* <ColField required title="默认速度">
+              <Field
+                clearable
+                name="playSpeed"
+                modelValue={this.speed}
+                rules={[{ required: true, message: '请输入默认速度' }]}
+                onUpdate:modelValue={val => (this.speed = val)}
+                class={styles['clear-px']}
+                placeholder="请输入默认速度"
+              />
+            </ColField> */}
+            <ColField required title="曲目声部">
+              <Field
+                is-link
+                readonly
+                class={styles['clear-px']}
+                placeholder="请选择曲目声部"
+                name="vlewSubjectList"
+                modelValue={this.vlewSubjectList?.value}
+                rules={[{ required: true, message: '请选择曲目声部' }]}
+                // onUpdate:modelValue={val => (this.selectedSubjectList = )}
+                onClick={() => (this.showPicker = true)}
+              ></Field>
+            </ColField>
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              XML文件中,选择的曲目声部需要在总谱的置顶位置。
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField
+              border={false}
+              required
+              title="曲目标签"
+              v-slots={{
+                right: () => (
+                  <Button
+                    class={styles.select}
+                    round
+                    type="primary"
+                    size="small"
+                    onClick={() => (this.tagVisibility = true)}
+                  >
+                    选择
+                  </Button>
+                )
+              }}
+            >
+              <Field
+                name="tags"
+                modelValue={this.tags.length ? 1 : undefined}
+                rules={[{ required: true, message: '请选择曲目标签' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    this.tags.length > 0 ? (
+                      this.tags.map((item: any) => (
+                        <Tag type="primary" size="large" class={styles.tags}>
+                          {this.tagsNames[item]}
+                        </Tag>
+                      ))
+                    ) : (
+                      <Empty
+                        style={{ width: '100%' }}
+                        description="请选择曲目标签"
+                        imageSize={0}
+                      />
+                    )
+                }}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <ColFieldGroup class={styles.area}>
+            {/* <ColField required title="是否评测" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.canEvaluate}
+                onUpdate:modelValue={val => (this.canEvaluate = val)}
+              >
+                {Object.keys(teachercanEvaluateType).map((item: string) => {
+                  const isActive = item === String(this.canEvaluate)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teachercanEvaluateType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField> */}
+            {/* <ColField required title="指法展示" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.showFingering}
+                onUpdate:modelValue={val => (this.showFingering = val)}
+              >
+                {Object.keys(teachershowFingeringType).map((item: string) => {
+                  const isActive = item === String(this.showFingering)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teachershowFingeringType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField> */}
+
+            {/* 判断是否是审核版本 */}
+            {!this.checked && (
+              <ColField required title="是否收费" border={false}>
+                <RadioGroup
+                  class={styles['radio-group']}
+                  modelValue={this.chargeType}
+                  onUpdate:modelValue={val => {
+                    this.chargeType = Number(val)
+                  }}
+                >
+                  {Object.keys(teacherChargeType).map((item: string) => {
+                    const isActive = item === String(this.chargeType)
+                    const type = isActive ? 'primary' : 'default'
+                    return (
+                      <Radio class={styles.radio} name={item}>
+                        <Tag size="large" plain={isActive} type={type}>
+                          {teacherChargeType[item]}
+                        </Tag>
+                      </Radio>
+                    )
+                  })}
+                </RadioGroup>
+              </ColField>
+            )}
+
+            {this.chargeType === 2 && (
+              <ColField required title="收费价格">
+                <Field
+                  clearable
+                  class={styles['clear-px']}
+                  placeholder="请输入收费价格"
+                  formatter={this.onFormatter}
+                  v-slots={{ button: () => '元' }}
+                  modelValue={this.musicPrice}
+                  rules={[
+                    { required: true, validator, message: '请输入收费价格' }
+                  ]}
+                  onUpdate:modelValue={val => (this.musicPrice = val)}
+                />
+              </ColField>
+            )}
+            {/* <ColField required title="支持简谱" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.notation}
+                onUpdate:modelValue={val => {
+                  this.notation = Number(val)
+                }}
+              >
+                {Object.keys(teacherNotationType).map((item: string) => {
+                  const isActive = item === String(this.notation)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teacherNotationType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField> */}
+            <ColField required title="是否精品乐谱" border={false}>
+              <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>
+            </ColField>
+          </ColFieldGroup>
+          {this.chargeType === 2 && (
+            <div class={styles.rule}>
+              <p>扣除手续费后该曲目预计收入为:</p>
+              <p>
+                每人:
+                <span>
+                  {(
+                    ((parseFloat(this.musicPrice || '0') || 0) *
+                      (100 - this.music_sheet_service_fee)) /
+                    100
+                  ).toFixed(2)}
+                </span>
+                元/人
+              </p>
+
+              <p>
+                您的乐谱收入在学员购买后{this.music_account_period}
+                天结算到您的账户中
+              </p>
+            </div>
+          )}
+        </div>
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={styles['button-area']}>
+            <Button
+              type="primary"
+              block
+              round
+              native-type="submit"
+              loading={this.submitLoading}
+            >
+              确认
+            </Button>
+          </div>
+        </Sticky>
+        <Popup
+          show={this.showPicker}
+          round
+          position="bottom"
+          teleport="body"
+          onUpdate:show={val => (this.showPicker = val)}
+        >
+          <Picker
+            defaultIndex={this.subJectIndex}
+            columnsFieldNames={{
+              text: 'value'
+            }}
+            columns={Object.entries(this.subjectListNames).map(
+              ([key, value]) => ({ label: key, value })
+            )}
+            onCancel={() => (this.showPicker = false)}
+            onConfirm={val => {
+              this.selectedSubjectList = val
+              this.vlewSubjectList = val
+              this.showPicker = false
+            }}
+          />
+        </Popup>
+
+        <Popup
+          show={this.subJectVisible}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate:show={val => (this.subJectVisible = val)}
+        >
+          <SubjectModel
+            subjectList={this.subjectList}
+            choiceSubjectIds={this.choiceSubjectIds}
+            onChoice={this.onChoice}
+            selectType="Radio"
+          />
+        </Popup>
+        <Popup
+          show={this.tagVisibility}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate:show={val => (this.tagVisibility = val)}
+        >
+          <SelectTag
+            onConfirm={this.onComfirm}
+            onCancel={() => {}}
+            rowSingle
+            defaultValue={this.tags.join(',')}
+            needAllButton={false}
+          />
+        </Popup>
+      </Form>
+    )
+  }
+})

+ 145 - 0
src/teacher/music/upload/index.module copy.less

@@ -0,0 +1,145 @@
+.form {
+  --van-notice-bar-background-color: var(--tag-bg-color);
+  --van-notice-bar-text-color: var(--van-tag-primary-color);
+}
+.container {
+  background-color: var(--col-background-color);
+  min-height: 100vh;
+  margin: 14px 0;
+  .area {
+    padding: 20px 20px 0;
+    margin-bottom: 12px;
+  }
+  .select {
+    font-size: 14px;
+    padding: 6px 12px;
+  }
+}
+
+.clear-px {
+  padding-left: 0;
+  padding-right: 0;
+}
+
+.rule {
+  font-size: 14px;
+  line-height: 27px;
+  color: var(--tips-color);
+  margin: 0 14px;
+  margin-bottom: 20px;
+  > p > span {
+    color: var(--strong--color);
+    font-weight: bold;
+  }
+}
+
+.button-area {
+  padding: 10px 14px;
+  background-color: var(--white);
+  box-shadow: 0 0 10px var(--box-shadow-color);
+}
+
+.radio-group {
+  display: flex;
+  margin-top: 14px;
+  .radio:first-child {
+    :global {
+      .van-radio__label {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+.radio {
+  :global {
+    .van-radio__icon {
+      display: none;
+    }
+    .van-tag--large {
+      width: 94px;
+      height: 30px;
+      font-size: 16px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .van-tag {
+      box-sizing: border-box;
+    }
+    .van-tag--default {
+      color: var(--van-tag-text-default-color);
+    }
+    .van-tag--primary {
+      background-color: var(--tag-bg-color);
+    }
+  }
+}
+
+.tags {
+  margin-right: 5px;
+  margin-bottom: 5px;
+  margin-top: 5px;
+}
+
+.file {
+  display: flex;
+  width: 100%;
+  > div {
+    flex: 1;
+    > span {
+      display: inline-block;
+      margin-left: 5px;
+    }
+  }
+  .delbtn {
+    padding: 0;
+    height: auto;
+    border: none;
+  }
+}
+
+.upbtn {
+  border: 1px solid #cfcfcf;
+  width: 100%;
+  background: #fbfbfb;
+  color: #666666;
+  height: 77px;
+  border-radius: 5px;
+  border-style: dashed;
+  i {
+    font-size: 24px;
+  }
+}
+
+.tips {
+  font-size: 12px;
+  color: #e0945a;
+  line-height: 18px;
+  padding: 15px 11px;
+  background: #fff3eb;
+  border-radius: 10px;
+  margin: 0 14px 12px;
+
+  .tipsTitle {
+    font-size: 14px;
+    font-weight: 600;
+    color: #e0945a;
+    line-height: 20px;
+    padding-bottom: 6px;
+  }
+
+  span {
+    color: #5aa9e0;
+  }
+}
+
+.imgContainer {
+  width: 150px;
+  height: 150px;
+  border-radius: 10px;
+  overflow: hidden;
+  margin: 0 0 12px;
+  position: relative;
+}

+ 284 - 17
src/teacher/music/upload/index.module.less

@@ -1,21 +1,214 @@
 .form {
   --van-notice-bar-background-color: var(--tag-bg-color);
   --van-notice-bar-text-color: var(--van-tag-primary-color);
+  min-height: 100%;
+  background: url('./images/banner-bg.png') no-repeat center top;
+  background-size: contain;
+  background-color: #BEEBFD;
 }
+
+.uploadMessage {
+  position: absolute;
+  top: -100px;
+  right: -14px;
+  font-weight: 500;
+  font-size: 12px;
+  color: #FFFFFF;
+  line-height: 17px;
+  border-radius: 12px 0px 0px 12px;
+  background: rgba(0, 0, 0, 0.3);
+  padding: 3px 4px 3px 6px;
+  cursor: pointer;
+}
+
+.titleImg {
+  position: absolute;
+  top: -58px;
+  left: 17px;
+  width: 129px;
+  height: 31px;
+}
+
+.area {
+  position: relative;
+  padding: 34px 0 12px;
+  margin: 22px 14px 12px;
+  border-radius: 16px;
+
+  &.topArea {
+    margin-top: 100px;
+  }
+
+  .section-title {
+    position: absolute;
+    top: -10px;
+    left: 50%;
+    margin-left: -72.5px;
+    width: 145px;
+    height: 36px;
+    background: url('./images/title-bg.png') no-repeat center;
+    background-size: contain;
+
+    &.section-title2 {
+      top: -8px;
+      background: url('./images/title-bg2.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+
+  :global {
+    .van-field {
+      padding: 14px 12px;
+    }
+
+    .van-field__label {
+      font-weight: 500;
+      font-size: 16px;
+      color: #000;
+      margin-right: 0;
+      flex: 1 auto;
+    }
+
+    .van-field__value {
+      font-size: 16px;
+    }
+
+  }
+
+  .hideValue {
+    :global {
+      .van-field__value {
+        display: none;
+      }
+    }
+  }
+
+  .fieldTypeBottom {
+    flex-direction: column;
+
+    &.musicTrack {
+      padding-top: 0px;
+
+      .fieldTitle {
+        font-weight: 500;
+        font-size: 14px;
+        color: #131415;
+      }
+
+      .titleTip {
+        font-weight: 400;
+        font-size: 14px;
+        color: #F71D1D !important;
+      }
+    }
+
+    :global {
+      .van-field__value {
+        padding-top: 12px;
+      }
+
+      .van-field__label {
+        width: 100%;
+        margin-right: 0;
+      }
+    }
+
+    .fieldTitle {
+      display: flex;
+      justify-content: space-between;
+
+      .titleName {
+        display: flex;
+        align-items: center;
+      }
+
+      i {
+        color: #ee0a24;
+      }
+
+      .iconQuestion {
+        display: inline-block;
+        width: 16px;
+        height: 16px;
+        margin-left: 6px;
+        background: url('./images/icon-question.png') no-repeat center;
+        background-size: contain;
+      }
+
+      .titleTip {
+        font-size: 12px;
+        color: #AAAAAA;
+      }
+    }
+  }
+
+  .textareaType {
+    :global {
+      .van-field__value {
+        margin-top: 12px;
+        background: #F6F8F9;
+        border-radius: 10px;
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+
+      .van-field__word-limit {
+        position: absolute;
+        top: -35px;
+        right: 0;
+        font-size: 12px;
+        color: #AAAAAA;
+      }
+    }
+  }
+
+  .inputControl {
+    :global {
+      .van-field__body {
+        justify-content: flex-end;
+      }
+    }
+
+    input {
+      background: #F6F8F9;
+      border-radius: 6px;
+      height: 32px;
+      text-align: center;
+      width: 70px;
+    }
+  }
+}
+
 .container {
   background-color: var(--col-background-color);
   min-height: 100vh;
   margin: 14px 0;
-  .area {
-    padding: 20px 20px 0;
-    margin-bottom: 12px;
-  }
+
+
+
   .select {
     font-size: 14px;
     padding: 6px 12px;
   }
 }
 
+.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;
@@ -26,22 +219,69 @@
   line-height: 27px;
   color: var(--tips-color);
   margin: 0 14px;
-  margin-bottom: 20px;
-  > p > span {
+
+  >p>span {
     color: var(--strong--color);
     font-weight: bold;
   }
 }
 
+.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;
+    }
+  }
+}
+
 .button-area {
   padding: 10px 14px;
-  background-color: var(--white);
-  box-shadow: 0 0 10px var(--box-shadow-color);
+
+  // background-color: var(--white);
+  // box-shadow: 0 0 10px var(--box-shadow-color);
+  :global {
+    .van-button {
+      // background: url('');
+      background: transparent;
+      padding: 0;
+      border: 0;
+      height: 50px;
+    }
+
+    .van-button:active:before {
+      opacity: 1;
+    }
+
+    .van-button__text {
+      height: 100%;
+    }
+  }
+
+  img {
+    width: 100%;
+    max-height: 100%;
+  }
 }
 
 .radio-group {
   display: flex;
-  margin-top: 14px;
+
   .radio:first-child {
     :global {
       .van-radio__label {
@@ -56,6 +296,7 @@
     .van-radio__icon {
       display: none;
     }
+
     .van-tag--large {
       width: 94px;
       height: 30px;
@@ -64,13 +305,17 @@
       display: flex;
       align-items: center;
       justify-content: center;
+      border-radius: 6px;
     }
+
     .van-tag {
       box-sizing: border-box;
     }
+
     .van-tag--default {
       color: var(--van-tag-text-default-color);
     }
+
     .van-tag--primary {
       background-color: var(--tag-bg-color);
     }
@@ -80,19 +325,30 @@
 .tags {
   margin-right: 5px;
   margin-bottom: 5px;
-  margin-top: 5px;
+  // margin-top: 5px;
+  background: #F2FFFC;
+  border-radius: 4px;
+  height: 28px;
+  font-size: 14px;
+  color: #00B2A7 !important;
+  padding-top: 0;
+  padding-bottom: 0;
+  border: 1px solid #9FE2DE;
 }
 
 .file {
   display: flex;
   width: 100%;
-  > div {
+
+  >div {
     flex: 1;
-    > span {
+
+    >span {
       display: inline-block;
       margin-left: 5px;
     }
   }
+
   .delbtn {
     padding: 0;
     height: auto;
@@ -103,14 +359,24 @@
 .upbtn {
   border: 1px solid #cfcfcf;
   width: 100%;
-  background: #fbfbfb;
   color: #666666;
-  height: 77px;
-  border-radius: 5px;
+  height: 48px;
+  background: #F6F8F9;
+  border-radius: 6px;
   border-style: dashed;
+
   i {
     font-size: 24px;
   }
+
+  :global {
+    .van-button__text {
+      max-width: 90%;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      white-space: nowrap;
+    }
+  }
 }
 
 .tips {
@@ -136,10 +402,11 @@
 }
 
 .imgContainer {
-  width: 150px;
-  height: 150px;
+  width: 101px !important;
+  height: 101px !important;
   border-radius: 10px;
   overflow: hidden;
   margin: 0 0 12px;
+  margin-top: 0 !important;
   position: relative;
 }

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 775 - 817
src/teacher/music/upload/index.tsx


+ 181 - 0
src/teacher/music/upload/instrumentModal/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;
+}

+ 108 - 0
src/teacher/music/upload/instrumentModal/index.tsx

@@ -0,0 +1,108 @@
+import { Ref, defineComponent, ref, watch } from 'vue'
+import { Tag, CheckboxGroup, Checkbox, RadioGroup, Radio, Button } from 'vant'
+import styles from './index.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'SelectTagChild',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    child: {
+      type: Array,
+      default: () => []
+    },
+    choiceSubjectIds: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { attrs, emit, expose }) {
+    const subjects: Ref<Array<any | number>> = ref([...props.choiceSubjectIds])
+    const list = ref([...props.child])
+    const onSelect = val => {
+      setTimeout(() => {
+        val.forEach(i => {
+          if (!subjects.value.includes(i)) {
+            subjects.value.push(i)
+          }
+        })
+      }, 100)
+    }
+
+    watch(
+      () => props.show,
+      () => {
+        if (!props.show) {
+          resetTags()
+        } else {
+          subjects.value = [...props.choiceSubjectIds]
+          list.value = [...props.child]
+        }
+      }
+    )
+
+    const resetTags = () => {
+      console.log(props.choiceSubjectIds)
+      subjects.value = [...props.choiceSubjectIds]
+      emit('close')
+    }
+
+    expose({
+      resetTags
+    })
+    return () => {
+      return (
+        <div class={styles.select}>
+          <h4 class={styles.title}>全部声部</h4>
+          <div class={styles.content}>
+            <CheckboxGroup
+              class={classNames(styles.childContent, styles['radio-group'])}
+              modelValue={subjects.value}
+              onUpdate:modelValue={val => {
+                onSelect!(val)
+              }}
+            >
+              {list.value.map((item: any) => (
+                <Checkbox
+                  key={item.id}
+                  name={item.id}
+                  class={styles.radio}
+                  onClick={() => {
+                    onSelect!([item.id])
+                  }}
+                >
+                  <Tag
+                    class={classNames(styles.item, 'van-ellipsis')}
+                    plain={!subjects.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', subjects.value)}
+            >
+              确认
+            </Button>
+          </footer>
+        </div>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,113 @@
+.wxPopupDialog {
+  // position: relative;
+  overflow: initial;
+
+  // margin-top: -160px;
+  &::before {
+    position: absolute;
+    content: ' ';
+    top: -23px;
+    left: 50%;
+    margin-left: -50px;
+    display: inline-block;
+    background: url('../images/bell.png') no-repeat top center;
+    background-size: contain;
+    width: 100px;
+    height: 60px;
+  }
+
+  &.dialogError {
+    &::before {
+      background: url('../images/error-icon.png') no-repeat top center;
+      background-size: contain;
+    }
+  }
+}
+
+.popupContainer {
+  background: url('../images/message-top-bg.png') no-repeat top center;
+  background-size: contain;
+  border-radius: 20px;
+  overflow: hidden;
+  padding-bottom: 16px;
+
+  .title1 {
+    padding-top: 57px;
+    text-align: center;
+    font-size: 18px;
+    font-weight: 600;
+    color: #000000;
+
+    span {
+      position: relative;
+      z-index: 1;
+
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0;
+        bottom: 0;
+        z-index: -1;
+        width: 100%;
+        height: 8px;
+        background: linear-gradient(to right, rgba(45, 199, 170, 1), rgba(91, 236, 255, 0.20));
+      }
+    }
+  }
+
+  .popupTips {
+    position: relative;
+    font-size: 15px;
+    color: #777777;
+    line-height: 21px;
+
+
+    // &::before {
+    //   content: '';
+    //   position: absolute;
+    //   left: 0;
+    //   bottom: 0;
+    //   height: 54px;
+    //   width: 100%;
+    //   background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 100%);
+    // }
+
+    .container {
+      max-height: 300px;
+      overflow-x: hidden;
+      overflow-y: auto;
+      padding: 16px 20px 0;
+    }
+
+    .cTitle {
+      font-weight: 600;
+      font-size: 15px;
+      color: #131415;
+      line-height: 23px;
+    }
+
+    .cContent {
+      font-size: 14px;
+      color: #777777;
+      line-height: 24px;
+      padding-bottom: 14px;
+
+      span {
+        color: #14BC9C;
+        cursor: pointer;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .button {
+    padding: 0 32px;
+    width: calc(100% - 70px);
+    margin: 0 auto;
+    height: 40px;
+    font-weight: 600;
+    font-size: 16px;
+    color: #FFFFFF;
+    line-height: 22px;
+  }
+}

+ 166 - 0
src/teacher/music/upload/message-tip/index.tsx

@@ -0,0 +1,166 @@
+import { Button, Popup } from 'vant'
+import { PropType, defineComponent, onMounted, ref, watch } from 'vue'
+import styles from './index.module.less'
+import { postMessage } from '@/helpers/native-message'
+
+export default defineComponent({
+  name: 'message-tip',
+  props: {
+    // 是否显示微信弹窗
+    show: {
+      type: Boolean,
+      default: true
+    },
+    type: {
+      type: String as PropType<'upload' | 'error' | 'origin'>,
+      default: 'upload'
+    },
+    title: {
+      type: String,
+      default: '温馨提示'
+    },
+    showButton: {
+      type: Boolean,
+      default: true
+    },
+    buttonText: {
+      type: String,
+      default: '我已知晓'
+    }
+  },
+  emits: ['confirm'],
+  setup(props, { emit }) {
+    const showPopup = ref(false)
+    onMounted(() => {
+      if (props.show) {
+        showPopup.value = true
+        return
+      }
+    })
+
+    watch(
+      () => [props.show],
+      () => {
+        if (props.show) {
+          showPopup.value = true
+        } else {
+          showPopup.value = false
+        }
+      }
+    )
+    // props.title, props.type
+    // watch(
+    //   () => [props.title, props.type],
+    //   () => {}
+    // )
+
+    // 详情
+    const onDetail = (type: string) => {
+      let url = `${location.origin}/teacher/#/registerProtocol`
+
+      if (type === 'question') {
+        // 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.html#/upload-protocol?type=music`
+      }
+
+      postMessage({
+        api: 'openWebView',
+        content: {
+          url,
+          orientation: 1,
+          isHideTitle: false
+        }
+      })
+    }
+    return () => (
+      <>
+        <Popup
+          v-model:show={showPopup.value}
+          round
+          style={{ width: '88%' }}
+          closeOnClickOverlay={false}
+          class={[
+            styles.wxPopupDialog,
+            props.type === 'error' ? styles.dialogError : ''
+          ]}
+        >
+          <div class={styles.popupContainer}>
+            <p class={styles.title1}>
+              <span>{props.title}</span>
+            </p>
+            <div class={styles.popupTips}>
+              {props.type === 'upload' && (
+                <div class={styles.container}>
+                  <p class={styles.cTitle}>注意事项:</p>
+                  <div class={styles.cContent}>
+                    1、必须是上传人自己参与制作的作品 <br />
+                    2、歌曲及歌曲信息中请勿涉及政治、宗教、广告、涉毒、犯罪、色情、低俗、暴力、血腥、消极等违规内容,违反者直接删除内容。多次违反则进行封号处理;
+                    <br />
+                    3、点击查看
+                    <span onClick={() => onDetail('protocol')}>
+                      《用户注册协议》
+                    </span>
+                    ,如果您上传了文件,即认为您完全同意并遵守该协议的内容。
+                  </div>
+                  <p class={styles.cTitle}>曲谱审核标准:</p>
+                  <div class={styles.cContent}>
+                    1、文件大小不要超过5MB,不符合版面规范的乐谱,审核未通过的不予上架,详情参考
+                    <span onClick={() => onDetail('music')}>
+                      《曲谱排版规范》
+                    </span>
+                    ;
+                    <br />
+                    2、XML与MIDI文件内容必须一致,推荐使用Sibelius打谱软件;导出设置:导出XML-未压缩(*.XML)/导出MIDI:音色-其他回放设备General
+                    MIDI、MIDI、MIDI文件类型-类型0、不要勾选“将弱拍小节导出为具有休止符的完整小节”。点击查看
+                    <span onClick={() => onDetail('question')}>
+                      《常见问题》
+                    </span>
+                  </div>
+                </div>
+              )}
+
+              {props.type === 'error' && (
+                <div class={styles.container}>
+                  <div class={styles.cContent}>
+                    声轨名称解析失败,请对照
+                    <span onClick={() => onDetail('music')}>
+                      《曲谱排版规范》
+                    </span>
+                    检查后重试
+                  </div>
+                </div>
+              )}
+
+              {props.type === 'origin' && (
+                <div class={styles.container}>
+                  <div class={styles.cContent}>
+                    1、同一首曲目不可重复上传,如有不同版本统一用“()”补充。举例:人生的旋转木马(长笛二重奏版)
+                    <br />
+                    2、曲目名后可添加曲目信息备注,包含但不限于曲目类型等。曲目名《XXX》,举例:人声的旋转木马《哈尔的移动城堡》(长笛二重奏版)
+                    <br />
+                    3、其他信息不要写在曲目名里,如歌手、上传人员昵称等。
+                  </div>
+                </div>
+              )}
+            </div>
+
+            {props.showButton && (
+              <Button
+                round
+                type="primary"
+                block
+                class={styles.button}
+                onClick={() => emit('confirm')}
+              >
+                {props.buttonText}
+              </Button>
+            )}
+          </div>
+        </Popup>
+      </>
+    )
+  }
+})

+ 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>
+      )
+    }
+  }
+})

+ 181 - 0
src/teacher/music/upload/subjectModal/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;
+}

+ 103 - 0
src/teacher/music/upload/subjectModal/index.tsx

@@ -0,0 +1,103 @@
+import { Ref, defineComponent, ref, watch } from 'vue'
+import { Tag, CheckboxGroup, Checkbox, RadioGroup, Radio, Button } from 'vant'
+import styles from './index.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'SelectTagChild',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    child: {
+      type: Array,
+      default: () => []
+    },
+    choiceSubjectIds: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { attrs, emit }) {
+    const subjects: Ref<Array<any | number>> = ref([...props.choiceSubjectIds])
+
+    const onSelect = val => {
+      setTimeout(() => {
+        val.forEach(i => {
+          if (!subjects.value.includes(i)) {
+            subjects.value.push(i)
+          }
+        })
+      }, 100)
+    }
+
+    watch(
+      () => props.show,
+      () => {
+        if (!props.show) {
+          resetTags()
+        } else {
+          subjects.value = [...props.choiceSubjectIds]
+        }
+      }
+    )
+
+    const resetTags = () => {
+      console.log(props.choiceSubjectIds)
+      subjects.value = [...props.choiceSubjectIds]
+      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={subjects.value}
+              onUpdate:modelValue={val => {
+                onSelect!(val)
+              }}
+            >
+              {props.child.map((item: any) => (
+                <Checkbox
+                  key={item.id}
+                  name={item.id}
+                  class={styles.radio}
+                  onClick={() => {
+                    onSelect!([item.id])
+                  }}
+                >
+                  <Tag
+                    class={classNames(styles.item, 'van-ellipsis')}
+                    plain={!subjects.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', subjects.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;

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott