Browse Source

Merge branch 'iteration-20240624' into online

lex 8 months ago
parent
commit
bba13c7978
28 changed files with 1884 additions and 447 deletions
  1. 28 2
      src/components/col-cropper/index.module.less
  2. 33 16
      src/components/col-cropper/index.tsx
  3. BIN
      src/components/col-upload/images/icon-close.png
  4. BIN
      src/components/col-upload/images/icon-delete.png
  5. BIN
      src/components/col-upload/images/icon-document.png
  6. 8 0
      src/components/col-upload/index.module.less
  7. 59 9
      src/components/col-upload/index.tsx
  8. 2 2
      src/router/routes-admin.ts
  9. BIN
      src/views/student-info/components/user-menu/images/6-active.png
  10. BIN
      src/views/student-info/components/user-menu/images/6.png
  11. 10 0
      src/views/student-info/components/user-menu/index.module.less
  12. BIN
      src/views/user-info/components/user-menu/images/6-active.png
  13. BIN
      src/views/user-info/components/user-menu/images/6.png
  14. 20 0
      src/views/user-info/components/user-menu/index.module.less
  15. 5 3
      src/views/user-info/music-class/list.tsx
  16. BIN
      src/views/user-info/music-operation/images/bell.png
  17. BIN
      src/views/user-info/music-operation/images/error-icon.png
  18. BIN
      src/views/user-info/music-operation/images/icon-question.png
  19. BIN
      src/views/user-info/music-operation/images/message-top-bg.png
  20. BIN
      src/views/user-info/music-operation/images/message-top-bg2.png
  21. 730 0
      src/views/user-info/music-operation/index copy.tsx
  22. 95 0
      src/views/user-info/music-operation/index.module copy.less
  23. 112 7
      src/views/user-info/music-operation/index.module.less
  24. 410 408
      src/views/user-info/music-operation/index.tsx
  25. 140 0
      src/views/user-info/music-operation/message-tip/index.module.less
  26. 123 0
      src/views/user-info/music-operation/message-tip/index.tsx
  27. 105 0
      src/views/user-info/music-operation/music-xml.ts
  28. 4 0
      vite.config.ts

+ 28 - 2
src/components/col-cropper/index.module.less

@@ -21,39 +21,58 @@
   }
 }
 
+.iconDelete {
+  position: absolute;
+  right: 6px;
+  top: 6px;
+  width: 18px;
+  height: 18px;
+  background: url('../col-upload/images/icon-delete.png') no-repeat center;
+  background-size: contain;
+  z-index: 9;
+  cursor: pointer;
+}
+
 .avatar-upload-preview_range,
 .avatar-upload-preview {
   width: 180px;
   height: 180px;
   box-shadow: 0 0 4px #ccc;
   overflow: hidden;
+
   img {
     background-color: #f7f7f7;
     height: 100%;
   }
 }
+
 .avatar-upload-preview_range {
   border-radius: 0;
 }
+
 .previewImg {
   padding-left: 50px;
   padding-top: 10px;
-  & > span {
+
+  &>span {
     display: block;
     color: #212121;
     font-size: 16px;
     padding-bottom: 15px;
   }
 }
+
 .operation {
   font-size: 24px;
   display: flex;
   align-items: center;
   margin-top: 20px;
-  & > i {
+
+  &>i {
     margin-left: 12px;
     cursor: pointer;
   }
+
   .icon-rate {
     display: inline-block;
     width: 20px;
@@ -62,27 +81,34 @@
     background-size: contain;
   }
 }
+
 .vue-cropper {
   border-radius: 5px;
   overflow: hidden;
 }
+
 :deep(.el-dialog) {
   margin-bottom: 10vh;
+
   .el-dialog__header {
     // background: #363d55;
     background: #fff;
     padding: 15px 20px 15px;
+
     .el-dialog__title {
       color: #212121;
     }
+
     .el-dialog__headerbtn .el-dialog__close {
       color: #212121;
     }
   }
+
   .el-dialog__body {
     padding-top: 0;
   }
 }
+
 :global {
   .before {
     max-width: 400px !important;

+ 33 - 16
src/components/col-cropper/index.tsx

@@ -33,6 +33,10 @@ export default defineComponent({
       type: Boolean,
       default: false
     },
+    delete: {
+      type: Boolean,
+      default: false
+    },
     bucket: {
       type: String,
       default: 'daya'
@@ -55,7 +59,11 @@ export default defineComponent({
     },
     cropUploadSuccess: {
       type: Function,
-      default: (data: string) => {}
+      default: (data: string) => ({})
+    },
+    onRemove: {
+      type: Function,
+      default: (data: string) => ({})
     },
     domSize: {
       type: Object,
@@ -76,6 +84,7 @@ export default defineComponent({
       // 删除图片
       this.$emit('update:modelValue', '')
     },
+
     //从本地选择文件
     async handleChange(info: any) {
       if (this.isStopRun) {
@@ -94,7 +103,7 @@ export default defineComponent({
     // 上传之前 格式与大小校验
     beforeUpload(file) {
       this.isStopRun = false
-      var fileType = file.type
+      const fileType = file.type
       if (fileType.indexOf('image') < 0) {
         ElMessage.warning('请上传图片')
         this.isStopRun = true
@@ -115,11 +124,8 @@ export default defineComponent({
       return isLtSize
     },
     error() {
-      this.remove()
-      this.loading = false
-    },
-    remove() {
       this.onDelete()
+      this.loading = false
     },
     //获取服务器返回的地址
     handleCropperSuccess(data: any) {
@@ -134,7 +140,7 @@ export default defineComponent({
     // 取消上传
     handleCropperClose() {
       this.loading = false
-      this.remove()
+      this.onDelete()
     },
     getBase64(img, callback) {
       const reader = new FileReader()
@@ -161,15 +167,26 @@ export default defineComponent({
             style={{ height: this.domSize.height, width: this.domSize.width }}
           >
             {this.modelValue ? (
-              <ElImage
-                src={this.modelValue}
-                fit="cover"
-                style={{
-                  height: this.domSize.height,
-                  width: this.domSize.width
-                }}
-                class={styles.uploadSection}
-              />
+              <div class="relative">
+                <ElImage
+                  src={this.modelValue}
+                  fit="cover"
+                  style={{
+                    height: this.domSize.height,
+                    width: this.domSize.width
+                  }}
+                  class={styles.uploadSection}
+                />
+                {this.delete && (
+                  <i
+                    class={styles.iconDelete}
+                    onClick={(e: any) => {
+                      e.stopPropagation()
+                      this.onRemove()
+                    }}
+                  ></i>
+                )}
+              </div>
             ) : (
               <div
                 class={[

BIN
src/components/col-upload/images/icon-close.png


BIN
src/components/col-upload/images/icon-delete.png


BIN
src/components/col-upload/images/icon-document.png


+ 8 - 0
src/components/col-upload/index.module.less

@@ -1,5 +1,10 @@
 .colUpload {
   line-height: 0;
+
+  .uploadFileMusic {
+    color: #14BC9C;
+    text-decoration-line: underline;
+  }
 }
 
 .uploadSection {
@@ -23,6 +28,7 @@
   align-items: center;
   padding: 0 15px;
   color: var(--el-text-color-regular);
+
   :global {
     .el-icon {
       margin-right: 5px;
@@ -42,6 +48,7 @@
       height: 42px !important;
       justify-content: center;
       margin-top: -20px !important;
+
       svg {
         width: 20px;
         height: 20px;
@@ -60,6 +67,7 @@
       margin-top: -33px;
       height: 85px;
     }
+
     .el-loading-mask {
       height: 50px;
       border-radius: 10px;

+ 59 - 9
src/components/col-upload/index.tsx

@@ -3,6 +3,8 @@ import { defineComponent, PropType } from 'vue'
 import { Document } from '@element-plus/icons-vue'
 import styles from './index.module.less'
 import iconUpload from './images/icon_upload.png'
+import iconDocument from './images/icon-document.png'
+import iconDelete from './images/icon-close.png'
 import request from '@/helpers/request'
 import { getUploadSign, onOnlyFileUpload } from '@/helpers/oss-file-upload'
 
@@ -13,6 +15,11 @@ export default defineComponent({
       type: String,
       default: ''
     },
+    /** 当前类型 */
+    type: {
+      type: String,
+      default: ''
+    },
     uploadType: {
       type: String as PropType<'image' | 'file'>,
       default: 'image'
@@ -43,7 +50,15 @@ export default defineComponent({
     },
     onChange: {
       type: Function,
-      default: () => {}
+      default: () => ({})
+    },
+    onRemove: {
+      type: Function,
+      default: () => ({})
+    },
+    btnText: {
+      type: String,
+      default: '上传文件'
     }
   },
   data() {
@@ -205,16 +220,42 @@ export default defineComponent({
                   class={styles.uploadSection}
                 />
               ) : (
-                <div class={styles.uploadFile}>
-                  <ElIcon>
-                    <Document />
+                <div
+                  class={[
+                    styles.uploadFile,
+                    this.disabled && 'cursor-not-allowed'
+                  ]}
+                >
+                  <ElIcon size={20}>
+                    {this.type === 'music' ? (
+                      <img src={iconDocument} />
+                    ) : (
+                      <Document />
+                    )}
                   </ElIcon>
                   <span
-                    class="whitespace-nowrap overflow-hidden text-ellipsis"
+                    class="whitespace-nowrap overflow-hidden text-ellipsis flex-1"
                     style={{ lineHeight: '1.2' }}
                   >
                     {this.fileName(this.modelValue)}
                   </span>
+
+                  {!this.disabled && (
+                    <ElIcon
+                      size={18}
+                      style="margin-right: 0;"
+                      class="ml-1 cursor-pointer"
+                    >
+                      <img
+                        src={iconDelete}
+                        onClick={e => {
+                          e.stopPropagation()
+                          this.$emit('update:modelValue', '')
+                          this.onRemove()
+                        }}
+                      />
+                    </ElIcon>
+                  )}
                 </div>
               )
             ) : this.uploadType === 'image' ? (
@@ -228,11 +269,20 @@ export default defineComponent({
                 <p>{this.tips}</p>
               </div>
             ) : (
-              <div class={styles.uploadFile}>
-                <ElIcon>
-                  <Document />
+              <div
+                class={[
+                  styles.uploadFile,
+                  this.type === 'music' ? styles.uploadFileMusic : ''
+                ]}
+              >
+                <ElIcon size={20}>
+                  {this.type === 'music' ? (
+                    <img src={iconDocument} />
+                  ) : (
+                    <Document />
+                  )}
                 </ElIcon>
-                上传文件
+                {this.btnText}
               </div>
             )}
           </div>

+ 2 - 2
src/router/routes-admin.ts

@@ -185,7 +185,7 @@ export default [
         path: '/userInfo/musicClass',
         name: 'userInfoMusicClass',
         component: () => import('@/views/user-info/music-class'),
-        meta: { title: '上传曲谱', index: 5, isdark: true }
+        meta: { title: '上传曲谱', index: 6, isdark: true }
       },
       {
         path: '/userInfo/musicOperation',
@@ -193,7 +193,7 @@ export default [
         component: () => import('@/views/user-info/music-operation'),
         meta: {
           title: '上传曲谱',
-          index: 4,
+          index: 6,
           hidden: true,
           activeMenu: 'userInfoMusicClass',
           isdark: true

BIN
src/views/student-info/components/user-menu/images/6-active.png


BIN
src/views/student-info/components/user-menu/images/6.png


+ 10 - 0
src/views/student-info/components/user-menu/index.module.less

@@ -1,26 +1,32 @@
 .menuItem {
   box-sizing: border-box;
   border-right: 4px solid transparent;
+
   .icon1 {
     background: url('./images/1.png') no-repeat center;
     background-size: contain;
   }
+
   .icon2 {
     background: url('./images/2.png') no-repeat center;
     background-size: contain;
   }
+
   .icon3 {
     background: url('./images/3.png') no-repeat center;
     background-size: contain;
   }
+
   .icon4 {
     background: url('./images/4.png') no-repeat center;
     background-size: contain;
   }
+
   .icon5 {
     background: url('./images/5.png') no-repeat center;
     background-size: contain;
   }
+
   &.active,
   &:hover {
     background-color: #e9fff8;
@@ -31,18 +37,22 @@
       background: url('./images/1-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon2 {
       background: url('./images/2-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon3 {
       background: url('./images/3-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon4 {
       background: url('./images/4-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon5 {
       background: url('./images/5-active.png') no-repeat center;
       background-size: contain;

BIN
src/views/user-info/components/user-menu/images/6-active.png


BIN
src/views/user-info/components/user-menu/images/6.png


+ 20 - 0
src/views/user-info/components/user-menu/index.module.less

@@ -1,26 +1,37 @@
 .menuItem {
   box-sizing: border-box;
   border-right: 4px solid transparent;
+
   .icon1 {
     background: url('./images/1.png') no-repeat center;
     background-size: contain;
   }
+
   .icon2 {
     background: url('./images/2.png') no-repeat center;
     background-size: contain;
   }
+
   .icon3 {
     background: url('./images/3.png') no-repeat center;
     background-size: contain;
   }
+
   .icon4 {
     background: url('./images/4.png') no-repeat center;
     background-size: contain;
   }
+
   .icon5 {
     background: url('./images/5.png') no-repeat center;
     background-size: contain;
   }
+
+  .icon6 {
+    background: url('./images/6.png') no-repeat center;
+    background-size: contain;
+  }
+
   &.active,
   &:hover {
     background-color: #e9fff8;
@@ -31,21 +42,30 @@
       background: url('./images/1-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon2 {
       background: url('./images/2-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon3 {
       background: url('./images/3-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon4 {
       background: url('./images/4-active.png') no-repeat center;
       background-size: contain;
     }
+
     .icon5 {
       background: url('./images/5-active.png') no-repeat center;
       background-size: contain;
     }
+
+    .icon6 {
+      background: url('./images/6-active.png') no-repeat center;
+      background-size: contain;
+    }
   }
 }

+ 5 - 3
src/views/user-info/music-class/list.tsx

@@ -48,6 +48,7 @@ export default defineComponent({
       this.loading = true
       try {
         const { data } = await request.post(
+          // '/api-website/musicSheetAuthRecord/list',
           '/api-website/music/sheet/teacher/list',
           {
             data: {
@@ -137,15 +138,16 @@ export default defineComponent({
                 {this.list.map((item: any) => (
                   <MusicItem
                     onClick={(item: any) => {
-                      if (['PASS', 'DOING'].includes(this.auditStatus)) {
+                      if (['PASS'].includes(this.auditStatus)) {
                         this.$router.push({
                           path: '/muiscDetial',
                           query: { id: item.id }
                         })
                       }
                       if (
-                        this.auditStatus === 'UNPASS' ||
-                        this.auditStatus === 'OUT_SALE'
+                        ['DOING', 'UNPASS', 'OUT_SALE'].includes(
+                          this.auditStatus
+                        )
                       ) {
                         console.log(item)
                         this.$router.push({

BIN
src/views/user-info/music-operation/images/bell.png


BIN
src/views/user-info/music-operation/images/error-icon.png


BIN
src/views/user-info/music-operation/images/icon-question.png


BIN
src/views/user-info/music-operation/images/message-top-bg.png


BIN
src/views/user-info/music-operation/images/message-top-bg2.png


+ 730 - 0
src/views/user-info/music-operation/index copy.tsx

@@ -0,0 +1,730 @@
+import ColCropper from '@/components/col-cropper'
+import ColUpload from '@/components/col-upload'
+import request from '@/helpers/request'
+import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
+import { getCodeBaseUrl } from '@/helpers/utils'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElOption,
+  ElOptionGroup,
+  ElRadioButton,
+  ElRadioGroup,
+  ElSelect,
+  ElDialog,
+  ElMessage
+} from 'element-plus'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+
+export type BackgroundMp3 = {
+  url?: string
+  track?: string
+}
+
+export const validator = (rule, value, callback) => {
+  console.log(value)
+  if (value == '') {
+    callback(new Error('请输入收费价格'))
+  } else if (Number(value) <= 0) {
+    callback(new Error('收费金额必须大于0'))
+  } else {
+    callback()
+  }
+}
+
+export default defineComponent({
+  name: 'music-operation',
+  data() {
+    const query = this.$route.query
+    return {
+      type: query.type || 'create',
+      subjectList: [],
+      tagList: [],
+      submitLoading: false,
+      reason: '',
+      form: {
+        titleImg: '',
+        accompanimentType: 'HOMEMODE',
+        audioType: 'MP3',
+        xmlFileUrl: '',
+
+        mp3Url: '',
+        bgmp3Url: '',
+        midiUrl: '',
+        musicSheetName: '',
+        composer: '',
+        musicSubject: null as any,
+        tags: [] as string[],
+        hasBeat: 0,
+        notation: 1,
+        canEvaluate: 1,
+        showFingering: 1,
+        chargeType: 0,
+        exquisiteFlag: 0, // 是否精品
+        paymentType: '',
+        musicPrice: '',
+        backgroundMp3s: [
+          {
+            url: '',
+            track: ''
+          }
+        ] as BackgroundMp3[]
+      },
+      radioList: [], // 选中的人数
+      tagStatus: false,
+      music_sheet_service_fee: 0,
+      music_account_period: 0
+    }
+  },
+  async mounted() {
+    document.title = this.type === 'create' ? '新建曲谱' : '编辑曲谱'
+    try {
+      await request
+        .get('/api-website/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
+            }
+          })
+        })
+      await request.get('/api-website/open/subject/subjectSelect').then(res => {
+        this.subjectList = res.data || []
+      })
+
+      await request.get('/api-website/open/MusicTag/tree').then(res => {
+        this.tagList = res.data || []
+      })
+
+      if (this.$route.query.id) {
+        this.setDetail(this.$route.query.id as string)
+      }
+    } catch {}
+  },
+  methods: {
+    async setDetail(id: string) {
+      try {
+        const res = await request.get(
+          '/api-website/open/music/sheet/detail/' + id
+        )
+        this.form.chargeType = res.data.chargeType === 'FREE' ? 0 : 2
+        this.form.exquisiteFlag = res.data.exquisiteFlag
+        this.form.showFingering = res.data.showFingering
+        this.form.notation = res.data.notation
+        this.form.canEvaluate = res.data.canEvaluate
+        if (this.form.chargeType) {
+          this.form.musicPrice = res.data.musicPrice
+        }
+
+        this.form.composer = res.data.composer
+        this.form.musicSheetName = res.data.musicSheetName
+        this.form.audioType = res.data.audioType
+        this.form.musicSubject = Number(res.data.musicSubject)
+
+        const musicTag = res.data.musicTag.split(',')
+
+        const filterMusicTag = musicTag.filter((el: any) => {
+          return el != ''
+        })
+        this.form.tags = filterMusicTag.map((item: any) => {
+          return Number(item)
+        })
+
+        this.radioList = musicTag.map((item: any) => {
+          return Number(item)
+        })
+
+        this.form.xmlFileUrl = res.data.xmlFileUrl
+        this.form.accompanimentType = res.data.accompanimentType
+        this.form.titleImg = res.data.titleImg
+
+        // this.form.audioType = res.data.mp3Type
+
+        if (this.form.audioType === 'MP3') {
+          this.form.hasBeat = res.data.hasBeat || 0
+          this.form.mp3Url =
+            res.data.audioFileUrl || res.data.metronomeUrl || res.data.url
+        } else {
+          this.form.midiUrl = res.data.midiUrl
+        }
+
+        this.form.backgroundMp3s = (res.data.background || []).map(
+          (item: any, index: any) => {
+            if (index === 0) {
+              this.form.bgmp3Url = item.audioFileUrl || item.metronomeUrl
+            }
+            return {
+              url: this.form.hasBeat ? item.metronomeUrl : item.audioFileUrl,
+              track: item.track
+            }
+          }
+        )
+        this.reason = res.data.reason
+
+        // console.log(this.form.bgmp3Url)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    createSubmitData() {
+      const { form } = this
+      const beatType = form.hasBeat ? 'MP3_METRONOME' : 'MP3'
+      const mp3Type = form.audioType === 'MP3' ? beatType : 'MIDI'
+      return {
+        audioType: form.audioType,
+        sourceType: 'TEACHER',
+        mp3Type,
+        accompanimentType: form.accompanimentType,
+        titleImg: form.titleImg,
+        hasBeat: Number(form.hasBeat),
+        // url: form.hasBeat ? '' : form.mp3Url,
+        // metronomeUrl: form.hasBeat ? form.mp3Url : '',
+        audioFileUrl: form.mp3Url,
+        showFingering: Number(form.showFingering),
+        notation: Number(form.notation),
+        musicTag: form.tags.join(','),
+        musicSubject: form.musicSubject || undefined,
+        musicSheetName: form.musicSheetName,
+        midiUrl: form.midiUrl,
+        xmlFileUrl: form.xmlFileUrl,
+        canEvaluate: Number(form.canEvaluate),
+        chargeType: form.chargeType === 0 ? 'FREE' : 'CHARGE',
+        exquisiteFlag: form.exquisiteFlag,
+        paymentType: form.paymentType
+          ? form.paymentType
+          : form.chargeType === 0
+          ? 'FREE'
+          : 'CHARGE',
+        composer: form.composer,
+        musicPrice: form.chargeType === 0 ? 0 : form.musicPrice,
+        background: form.backgroundMp3s.map(item => ({
+          audioFileUrl: form.hasBeat ? '' : form.bgmp3Url,
+          track: item.track,
+          metronomeUrl: form.hasBeat ? form.bgmp3Url : ''
+        }))
+      }
+    },
+    onFormatter(e: any) {
+      e.target.value = verifyNumberIntegerAndFloat(e.target.value)
+    },
+    onSubmit() {
+      ;(this as any).$refs.form.validate(async (valid: any) => {
+        if (valid) {
+          this.submitLoading = true
+          console.log(this.createSubmitData(), 'createSubmitData')
+          try {
+            if (this.$route.query.id) {
+              await request.post('/api-website/music/sheet/update', {
+                data: {
+                  ...this.createSubmitData(),
+                  id: this.$route.query.id
+                }
+              })
+            } else {
+              await request.post('/api-website/music/sheet/create', {
+                data: this.createSubmitData()
+              })
+            }
+            this.submitLoading = false
+            ElMessage.success('上传成功')
+            sessionStorage.setItem('musicActiveName', 'DOING')
+            this.$router.back()
+          } catch (error) {
+            this.submitLoading = false
+          }
+        } else {
+          this.$nextTick(() => {
+            const isError = document.getElementsByClassName('is-error')
+            isError[0].scrollIntoView({
+              block: 'center',
+              behavior: 'smooth'
+            })
+          })
+          return false
+        }
+      })
+    },
+    onDetail(type: string) {
+      let url = `${getCodeBaseUrl('/teacher')}/#/registerProtocol`
+      if (type === 'question') {
+        url = `${getCodeBaseUrl('/teacher')}/muic-standard/question.html`
+      } else if (type === 'music') {
+        url = `${getCodeBaseUrl('/teacher')}/muic-standard/index.html`
+      }
+      window.open(url)
+    }
+  },
+  render() {
+    return (
+      <div class={styles.form}>
+        <div class="text-2xl font-semibold text-black leading-none px-6 py-5 ">
+          {this.type === 'create' ? '新建曲谱' : '编辑曲谱'}
+        </div>
+
+        <ElForm
+          size="large"
+          labelPosition="left"
+          labelWidth={'150px'}
+          model={this.form}
+          ref="form"
+          class="px-7 py-5"
+        >
+          <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>
+          <ElFormItem
+            label="上传XML"
+            prop="xmlFileUrl"
+            rules={[{ required: true, message: '请选择MusicXML文件' }]}
+          >
+            <ColUpload
+              v-model:modelValue={this.form.xmlFileUrl}
+              bucket={'cloud-coach'}
+              accept={'application/xml'}
+              uploadType={'file'}
+              extraTips="文件最大不能超过5MB"
+            />
+          </ElFormItem>
+          <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>
+          <ElFormItem
+            label="播放类型"
+            prop="audioType"
+            rules={[{ required: true, message: '请选择播放类型' }]}
+          >
+            <ElRadioGroup v-model={this.form.audioType}>
+              <ElRadioButton label={'MIDI'} class="mr-3 w-24">
+                MIDI
+              </ElRadioButton>
+              <ElRadioButton label={'MP3'} class="w-24">
+                MP3
+              </ElRadioButton>
+            </ElRadioGroup>
+          </ElFormItem>
+
+          {this.form.audioType === 'MP3' ? (
+            <>
+              {/* <ElFormItem
+                label="是否带节拍器"
+                prop="hasBeat"
+                rules={[{ required: true, message: '请选择是否带节拍器' }]}
+              >
+                <ElRadioGroup v-model={this.form.hasBeat}>
+                  <ElRadioButton label={0} class="mr-3 w-24">
+                    否
+                  </ElRadioButton>
+                  <ElRadioButton label={1} class="w-24">
+                    是
+                  </ElRadioButton>
+                </ElRadioGroup>
+              </ElFormItem> */}
+              <ElFormItem
+                label="伴奏类型"
+                prop="accompanimentType"
+                rules={[{ required: true, message: '请选择伴奏类型' }]}
+              >
+                <ElRadioGroup v-model={this.form.accompanimentType}>
+                  <ElRadioButton label={'HOMEMODE'} class="mr-3 w-24">
+                    自制伴奏
+                  </ElRadioButton>
+                  <ElRadioButton label={'COMMON'} class="w-24">
+                    普通伴奏
+                  </ElRadioButton>
+                </ElRadioGroup>
+              </ElFormItem>
+              <ElFormItem label="伴奏文件" prop="mp3Url">
+                <ColUpload
+                  v-model:modelValue={this.form.mp3Url}
+                  bucket={'cloud-coach'}
+                  accept={'.mp3'}
+                  uploadType={'file'}
+                  size={8}
+                  extraTips="文件最大不能超过8MB"
+                />
+              </ElFormItem>
+            </>
+          ) : (
+            <>
+              <ElFormItem
+                label="伴奏类型"
+                prop="accompanimentType"
+                rules={[{ required: true, message: '请选择伴奏类型' }]}
+              >
+                <ElRadioGroup v-model={this.form.accompanimentType}>
+                  <ElRadioButton label={'HOMEMODE'} class="mr-3 w-24">
+                    自制伴奏
+                  </ElRadioButton>
+                  <ElRadioButton label={'COMMON'} class="w-24">
+                    普通伴奏
+                  </ElRadioButton>
+                </ElRadioGroup>
+              </ElFormItem>
+              <ElFormItem
+                label="MIDI文件"
+                prop="midiUrl"
+                rules={[{ required: true, message: '请选择MIDI文件' }]}
+              >
+                <ColUpload
+                  v-model:modelValue={this.form.midiUrl}
+                  bucket={'cloud-coach'}
+                  accept={'.midi,.mid'}
+                  uploadType={'file'}
+                  size={8}
+                  extraTips="文件最大不能超过8MB"
+                />
+              </ElFormItem>
+            </>
+          )}
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              1、推荐上传自制伴奏,伴奏和谱面必须对齐。自制伴奏可以设置更高的收费标准。
+              <br />
+              2、普通伴奏如果涉及到版权纠纷,根据
+              <span onClick={() => this.onDetail('protocol')}>
+                《用户注册协议》
+              </span>
+              平台有权进行下架处理。
+            </div>
+          </div>
+          {this.form.audioType === 'MP3' && (
+            <ElFormItem
+              label="原音文件"
+              prop="bgmp3Url"
+              rules={[{ required: true, message: '请选择原音文件' }]}
+            >
+              <ColUpload
+                v-model:modelValue={this.form.bgmp3Url}
+                bucket={'cloud-coach'}
+                accept={'.mp3'}
+                uploadType={'file'}
+                size={8}
+                extraTips="文件最大不能超过8MB"
+              />
+            </ElFormItem>
+          )}
+          <ElFormItem
+            label="曲目名称"
+            prop="musicSheetName"
+            rules={[{ required: true, message: '请输入曲目名称' }]}
+          >
+            <ElInput
+              v-model={this.form.musicSheetName}
+              placeholder="请选择曲目名称"
+            />
+          </ElFormItem>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              1、同一首曲目不可重复上传,如有不同版本统一用“()”补充。举例:人生的旋转木马(长笛二重奏版)。
+              <br />
+              2、曲目名后可添加曲目信息备注,包含但不限于曲目类型等。曲目名《xxxx》,举例:人生的旋转木马《哈尔的移动城堡》(长笛二重奏版)
+              <br />
+              3、其他信息不要写在曲目名里,如歌手、上传人员昵称等。
+            </div>
+          </div>
+          <ElFormItem
+            label="曲谱封面"
+            prop="titleImg"
+            rules={[
+              {
+                required: true,
+                message: '请上传曲谱封面'
+              }
+            ]}
+          >
+            <ColCropper
+              modelValue={this.form.titleImg}
+              bucket={'cloud-coach'}
+              cropUploadSuccess={(data: any) => {
+                this.form.titleImg = data
+              }}
+              domSize={{ height: '150px' }}
+              options={{
+                title: '曲谱封面',
+                enlarge: 2,
+                autoCropWidth: 300,
+                autoCropHeight: 300
+              }}
+            />
+          </ElFormItem>
+          <ElFormItem
+            label="艺术家"
+            prop="composer"
+            rules={[{ required: true, message: '请输入艺术家' }]}
+          >
+            <ElInput v-model={this.form.composer} placeholder="请输入艺术家" />
+          </ElFormItem>
+          <ElFormItem
+            label="曲目声部"
+            prop="musicSubject"
+            rules={[
+              { required: true, message: '请选择曲目声部', trigger: 'change' }
+            ]}
+          >
+            <ElSelect
+              filterable
+              v-model={this.form.musicSubject}
+              placeholder="请选择曲目声部"
+              class="w-full"
+            >
+              {this.subjectList.map((group: any) => (
+                <ElOptionGroup key={group.id} label={group.name}>
+                  {group.subjects &&
+                    group.subjects.map((item: any) => (
+                      <ElOption
+                        key={item.id}
+                        value={item.id}
+                        label={item.name}
+                      />
+                    ))}
+                </ElOptionGroup>
+              ))}
+            </ElSelect>
+          </ElFormItem>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              XML文件中,选择的曲目声部需要在总谱的置顶位置。
+            </div>
+          </div>
+          <ElFormItem
+            label="曲目标签"
+            prop="tags"
+            rules={[{ required: true, message: '请选择曲目标签' }]}
+          >
+            {/* <div class="w-full relative"> */}
+            {/* <div
+                class=" w-full block h-[42px] absolute top-0 left-0 z-10"
+                onClick={() => {
+                  console.log(111)
+                  this.tagStatus = true
+                }}
+              ></div> */}
+            <ElSelect
+              multiple
+              v-model={this.form.tags}
+              placeholder="请选择曲目标签"
+              multipleLimit={3}
+              class="w-full"
+            >
+              {this.tagList.map((item: any) => (
+                <ElOption key={item.id} value={item.id} label={item.name} />
+              ))}
+            </ElSelect>
+            {/* </div> */}
+          </ElFormItem>
+          {/* <ElFormItem
+            label="支持简谱"
+            prop="notation"
+            rules={[{ required: true, message: '请选择是否支持简谱' }]}
+          >
+            <ElRadioGroup v-model={this.form.notation}>
+              <ElRadioButton label={0} class="mr-3 w-24">
+                否
+              </ElRadioButton>
+              <ElRadioButton label={1} class="w-24">
+                是
+              </ElRadioButton>
+            </ElRadioGroup>
+          </ElFormItem> */}
+          {/* <ElFormItem
+            label="是否评测"
+            prop="canEvaluate"
+            rules={[{ required: true, message: '请选择是否评测' }]}
+          >
+            <ElRadioGroup v-model={this.form.canEvaluate}>
+              <ElRadioButton label={0} class="mr-3 w-24">
+                否
+              </ElRadioButton>
+              <ElRadioButton label={1} class="w-24">
+                是
+              </ElRadioButton>
+            </ElRadioGroup>
+          </ElFormItem> */}
+          {/* <ElFormItem
+            label="指法展示"
+            prop="showFingering"
+            rules={[{ required: true, message: '请选择指法展示' }]}
+          >
+            <ElRadioGroup v-model={this.form.showFingering}>
+              <ElRadioButton label={0} class="mr-3 w-24">
+                否
+              </ElRadioButton>
+              <ElRadioButton label={1} class="w-24">
+                是
+              </ElRadioButton>
+            </ElRadioGroup>
+          </ElFormItem> */}
+          <ElFormItem
+            label="是否收费"
+            prop="chargeType"
+            rules={[{ required: true, message: '请选择是否收费' }]}
+          >
+            <ElRadioGroup v-model={this.form.chargeType}>
+              <ElRadioButton label={0} class="mr-3 w-24">
+                否
+              </ElRadioButton>
+              <ElRadioButton label={2} class="w-24">
+                是
+              </ElRadioButton>
+            </ElRadioGroup>
+          </ElFormItem>
+          {this.form.chargeType === 2 && (
+            <>
+              <ElFormItem
+                label="收费价格"
+                prop="musicPrice"
+                rules={[{ required: true, validator }]}
+              >
+                <ElInput
+                  v-model={this.form.musicPrice}
+                  placeholder="请输入收费价格"
+                  // @ts-ignore
+                  maxlength={5}
+                  onKeyup={this.onFormatter}
+                  v-slots={{
+                    suffix: () => <span class="text-base text-[#999]">元</span>
+                  }}
+                />
+              </ElFormItem>
+
+              <ElFormItem>
+                <div class={styles.rule}>
+                  <p>扣除手续费后该曲目预计收入为:</p>
+                  <p>
+                    每人:
+                    <span>
+                      {((parseFloat(this.form.musicPrice || '0') || 0) *
+                        (100 - this.music_sheet_service_fee)) /
+                        100}
+                    </span>
+                    元/人
+                  </p>
+                  <p>
+                    您的乐谱收入在学员购买后{this.music_account_period}
+                    天结算到您的账户中
+                  </p>
+                </div>
+              </ElFormItem>
+            </>
+          )}
+          <ElFormItem
+            label="是否精品乐谱"
+            prop="exquisiteFlag"
+            rules={[{ required: true, message: '请选择是否精品乐谱' }]}
+          >
+            <ElRadioGroup v-model={this.form.exquisiteFlag}>
+              <ElRadioButton label={0} class="mr-3 w-24">
+                否
+              </ElRadioButton>
+              <ElRadioButton label={1} class="w-24">
+                是
+              </ElRadioButton>
+            </ElRadioGroup>
+          </ElFormItem>
+        </ElForm>
+        <div class="text-center pt-6 pb-7">
+          <ElButton
+            class="!w-44 !h-[48px]"
+            round
+            onClick={() => {
+              this.$router.back()
+            }}
+          >
+            取消
+          </ElButton>
+          <ElButton
+            type="primary"
+            class="!w-44 !h-[48px]"
+            round
+            onClick={this.onSubmit}
+            loading={this.submitLoading}
+          >
+            提交审核
+          </ElButton>
+        </div>
+
+        {/* <ElDialog
+          modelValue={this.tagStatus}
+          onUpdate:modelValue={val => (this.tagStatus = val)}
+          width="35%"
+          title="全部标签"
+        >
+          {this.tagList.map((item: any, index: number) => (
+            <div class={[styles.tags, 'py-2']}>
+              <div class="text-sm pb-2">{item.name}</div>
+              {item.children.map((child: any) => (
+                <ElRadioGroup v-model={this.radioList[index]} class="pb-2">
+                  <ElRadioButton label={child.id} class="mr-3">
+                    {child.name}
+                  </ElRadioButton>
+                </ElRadioGroup>
+              ))}
+            </div>
+          ))}
+          <div class="text-center pt-2">
+            <ElButton
+              class="!w-36 !h-[48px]"
+              round
+              size="large"
+              onClick={() => {
+                this.radioList = []
+              }}
+            >
+              重置
+            </ElButton>
+            <ElButton
+              class="!w-36 !h-[48px]"
+              round
+              size="large"
+              type="primary"
+              onClick={() => {
+                this.form.tags = this.radioList.filter((el: any) => {
+                  return el != ''
+                })
+                this.tagStatus = false
+                ;(this as any).$refs.form.clearValidate('tags')
+              }}
+            >
+              确认
+            </ElButton>
+          </div>
+        </ElDialog> */}
+      </div>
+    )
+  }
+})

+ 95 - 0
src/views/user-info/music-operation/index.module copy.less

@@ -0,0 +1,95 @@
+.form,
+.tags {
+  --el-border-radius-small: 10px !important;
+  --el-component-size-large: 48px;
+  :global {
+    .el-input,
+    .el-select--large,
+    .el-form-item--large .el-form-item__label {
+      height: 48px;
+      line-height: 48px;
+    }
+    .el-form-item__label {
+      font-size: 16px;
+      color: rgba(0, 0, 0, 0.85);
+    }
+    .el-radio-button__inner {
+      border: var(--el-border);
+      border-radius: var(--el-border-radius-base) !important;
+      width: 100%;
+      padding: 16px 19px !important;
+    }
+    .el-radio-button__original-radio:checked + .el-radio-button__inner {
+      background-color: #e9fff8;
+      color: var(--el-color-primary);
+      box-shadow: none;
+    }
+
+    .el-dialog__body {
+      padding-top: 0;
+    }
+
+    .el-select .el-select__tags .el-tag {
+      background-color: #dffff8;
+      border: 1px solid #4bb39e;
+      color: #4bb39e !important;
+      border-radius: 10px;
+      margin: 0 6px 0 0;
+      height: 30px;
+    }
+    .el-tag .el-icon {
+      color: #4bb39e;
+      background-color: transparent;
+    }
+  }
+}
+
+// .from {
+//   --el-component-size-large: 48px;
+//   :global {
+//     .el-input,
+//     .el-select--large,
+//     .el-form-item--large .el-form-item__label {
+//       height: 48px;
+//       line-height: 48px;
+//     }
+//     .el-form-item__label {
+//       font-size: 16px;
+//       color: rgba(0, 0, 0, 0.85);
+//     }
+//   }
+// }
+
+.tips {
+  font-size: 12px;
+  color: #e0945a;
+  line-height: 18px;
+  padding: 15px 11px;
+  background: #fff3eb;
+  border-radius: 10px;
+  margin: 0 0 22px;
+
+  .tipsTitle {
+    font-size: 14px;
+    font-weight: 600;
+    color: #e0945a;
+    line-height: 20px;
+    padding-bottom: 6px;
+  }
+
+  span {
+    color: #5aa9e0;
+    cursor: pointer;
+  }
+}
+
+.rule {
+  font-size: 14px;
+  line-height: 27px;
+  color: #999;
+  margin: 0 14px;
+  > p > span {
+    color: #ff4e19;
+    font-weight: bold;
+  }
+}

+ 112 - 7
src/views/user-info/music-operation/index.module.less

@@ -2,24 +2,39 @@
 .tags {
   --el-border-radius-small: 10px !important;
   --el-component-size-large: 48px;
+
   :global {
+
     .el-input,
     .el-select--large,
     .el-form-item--large .el-form-item__label {
       height: 48px;
       line-height: 48px;
     }
+
+    .el-input-number {
+      width: 100%;
+
+      .el-input__inner {
+        text-align: left;
+      }
+    }
+
     .el-form-item__label {
+      font-weight: 600;
       font-size: 16px;
       color: rgba(0, 0, 0, 0.85);
+
     }
+
     .el-radio-button__inner {
       border: var(--el-border);
       border-radius: var(--el-border-radius-base) !important;
       width: 100%;
       padding: 16px 19px !important;
     }
-    .el-radio-button__original-radio:checked + .el-radio-button__inner {
+
+    .el-radio-button__original-radio:checked+.el-radio-button__inner {
       background-color: #e9fff8;
       color: var(--el-color-primary);
       box-shadow: none;
@@ -30,18 +45,65 @@
     }
 
     .el-select .el-select__tags .el-tag {
-      background-color: #dffff8;
-      border: 1px solid #4bb39e;
-      color: #4bb39e !important;
-      border-radius: 10px;
+      background: #F2FFFC;
+      border-radius: 4px;
+      border: 1px solid #9FE2DE;
+      font-size: 16px;
+      color: #00B2A7;
       margin: 0 6px 0 0;
       height: 30px;
     }
+
+    .el-textarea__inner {
+      min-height: 110px !important;
+      padding-top: 12px;
+      padding-bottom: 12px;
+    }
+
     .el-tag .el-icon {
-      color: #4bb39e;
+      // width: 10px;
+      // height: 10px;
+      color: #00B2A7;
       background-color: transparent;
     }
   }
+
+  .iconQesution {
+    display: inline-block;
+    width: 13px;
+    height: 14px;
+    background: url('./images/icon-question.png') no-repeat center;
+    background-size: contain;
+    margin-left: 3px;
+  }
+}
+
+.uploadCon {
+  :global {
+    .el-upload--text {
+      background-color: #fff;
+      border-radius: 10px;
+    }
+  }
+}
+
+.uploadTips {
+  padding: 4px 12px;
+  background: #2DC7AA;
+  border-radius: 15px;
+  font-weight: 600;
+  font-size: 16px;
+  color: #FFFFFF;
+  line-height: 22px;
+  cursor: pointer;
+}
+
+.formItem {
+  background: #F6F8F9;
+  border-radius: 10px;
+  padding: 16px;
+  width: 100%;
+  margin-bottom: 16px !important;
 }
 
 // .from {
@@ -88,8 +150,51 @@
   line-height: 27px;
   color: #999;
   margin: 0 14px;
-  > p > span {
+
+  >p>span {
     color: #ff4e19;
     font-weight: bold;
+    font-size: 16px;
+
+    span {
+      font-size: 14px;
+      padding-left: 4px;
+    }
   }
 }
+
+
+.fAlert {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 42px;
+  background: #F6F8F9;
+  font-weight: 600;
+  font-size: 18px;
+  color: #131415;
+  line-height: 25px;
+  margin-bottom: 32px;
+}
+
+
+.messageDialog,
+.messageDialog2 {
+  width: 618px;
+  border-radius: 18px;
+
+  :global {
+    .el-dialog__headerbtn {
+      font-size: 21px;
+    }
+
+    .el-dialog__header,
+    .el-dialog__body {
+      padding: 0;
+    }
+  }
+}
+
+.messageDialog2 {
+  width: 320px;
+}

File diff suppressed because it is too large
+ 410 - 408
src/views/user-info/music-operation/index.tsx


+ 140 - 0
src/views/user-info/music-operation/message-tip/index.module.less

@@ -0,0 +1,140 @@
+// .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;
+//   }
+// }
+
+.popupContainer {
+  background: url('../images/message-top-bg.png') no-repeat top center;
+  background-size: contain;
+  border-radius: 20px;
+  overflow: hidden;
+  padding-bottom: 25px;
+
+  &::before {
+    position: absolute;
+    content: ' ';
+    top: -36px;
+    left: 50%;
+    margin-left: -61px;
+    display: inline-block;
+    background: url('../images/bell.png') no-repeat top center;
+    background-size: contain;
+    width: 123px;
+    height: 71px;
+  }
+
+  &.popupContainerError {
+    &::before {
+      background: url('../images/error-icon.png') no-repeat top center;
+      background-size: contain;
+    }
+
+    .container {
+      padding: 16px 23px 0 !important;
+
+      .cContent {
+        padding-bottom: 25px;
+      }
+    }
+  }
+
+  .title1 {
+    padding-top: 52px;
+    text-align: center;
+    font-weight: 600;
+    font-size: 18px;
+    color: #131415;
+    line-height: 30px;
+
+    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));
+        opacity: 0.68;
+      }
+    }
+  }
+
+  .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: 400px;
+      overflow-x: hidden;
+      overflow-y: auto;
+      padding: 16px 42px 0;
+    }
+
+    .cTitle {
+      font-weight: 600;
+      font-size: 15px;
+      color: #333333;
+      line-height: 21px;
+    }
+
+    .cContent {
+      font-size: 15px;
+      color: #777777;
+      line-height: 25px;
+      padding-bottom: 14px;
+
+      span {
+        color: #14BC9C;
+        cursor: pointer;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .btnGroup {
+    text-align: center;
+  }
+
+  .button {
+    width: 260px;
+    height: 39px;
+    background: #2DC7AF;
+    border-radius: 10px;
+    font-weight: 600;
+    font-size: 15px;
+    color: #FFFFFF;
+    line-height: 21px;
+  }
+}

+ 123 - 0
src/views/user-info/music-operation/message-tip/index.tsx

@@ -0,0 +1,123 @@
+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'
+import { getCodeBaseUrl } from '@/helpers/utils'
+import { ElButton, ElScrollbar } from 'element-plus'
+
+export default defineComponent({
+  name: 'message-tip',
+  props: {
+    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)
+    // props.title, props.type
+    // watch(
+    //   () => [props.title, props.type],
+    //   () => {}
+    // )
+
+    // 详情
+    const onDetail = (type: string) => {
+      let url = `${getCodeBaseUrl('/teacher')}/#/registerProtocol`
+      if (type === 'question') {
+        url = `${getCodeBaseUrl('/teacher')}/muic-standard/question.html`
+      } else if (type === 'music') {
+        url = `${getCodeBaseUrl('/teacher')}/muic-standard/index.html`
+      }
+      window.open(url)
+    }
+    return () => (
+      <div
+        class={[
+          styles.popupContainer,
+          props.type === 'error' ? styles.popupContainerError : ''
+        ]}
+      >
+        <p class={styles.title1}>
+          <span>{props.title}</span>
+        </p>
+        <div class={styles.popupTips}>
+          {props.type === 'upload' && (
+            <ElScrollbar 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>
+            </ElScrollbar>
+          )}
+
+          {props.type === 'error' && (
+            <div class={styles.container}>
+              <div class={styles.cContent}>
+                声轨名称解析失败,请对照
+                <span onClick={() => onDetail('protocol')}>
+                  《曲谱排版规范》
+                </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 && (
+          <div class={styles.btnGroup}>
+            <ElButton
+              type="primary"
+              class={styles.button}
+              onClick={() => emit('confirm')}
+            >
+              {props.buttonText}
+            </ElButton>
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 105 - 0
src/views/user-info/music-operation/music-xml.ts

@@ -0,0 +1,105 @@
+export type FormatXMLResult = {
+  xml: string
+  speed: number
+  partNames: string[]
+}
+
+/** 获取到xml前处理,添加额外的节拍 */
+export const fillBeatXML = (xml: string): string => {
+  if (!xml) return ''
+  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+  const measures = xmlParse.getElementsByTagName('measure')
+  // let speed = -1
+  let beats = -1
+  let beatType = -1
+  // 小节中如果没有节点默认为休止符
+  for (const measure of Array.from(measures)) {
+    if (beats === -1 && measure.getElementsByTagName('beats').length) {
+      beats = parseInt(
+        measure.getElementsByTagName('beats')[0].textContent || '4'
+      )
+    }
+    if (beatType === -1 && measure.getElementsByTagName('beat-type').length) {
+      beatType = parseInt(
+        measure.getElementsByTagName('beat-type')[0].textContent || '4'
+      )
+    }
+    const divisions = parseInt(
+      measure.getElementsByTagName('divisions')[0]?.textContent || '256'
+    )
+    if (measure.getElementsByTagName('note').length === 0) {
+      const forwardTimeElement = measure
+        .getElementsByTagName('forward')[0]
+        ?.getElementsByTagName('duration')[0]
+      if (forwardTimeElement) {
+        forwardTimeElement.textContent = '0'
+      }
+      measure.innerHTML += `
+        <note>
+          <rest measure="yes"/>
+          <duration>${divisions * beats}</duration>
+          <voice>1</voice>
+          <type>whole</type>
+        </note>`
+    }
+  }
+  return new XMLSerializer().serializeToString(xmlParse)
+}
+
+export type FormatXMLInfo = {
+  speed: number
+  title: string
+  composer: string
+  partNames: string[]
+}
+
+/** 获取xml基本信息,标题,速度等信息 */
+export const getXmlInfo = (xml: string): FormatXMLInfo => {
+  const data: FormatXMLInfo = {
+    /** 速度 */
+    speed: 0,
+    /** 标题 */
+    title: '',
+    /** 作曲人 */
+    composer: '',
+    /** 声部列表 */
+    partNames: []
+  }
+
+  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+  data.title = xmlParse.getElementsByTagName('work-title')[0]?.textContent || ''
+  data.composer = xmlParse.getElementsByTagName('creator')[0]?.textContent || ''
+  const measures = xmlParse.getElementsByTagName('measure')
+
+  for (const item of Array.from(xmlParse.getElementsByTagName('part-name'))) {
+    if (item.textContent) {
+      data.partNames.push(item.textContent)
+    }
+  }
+
+  for (const measure of Array.from(measures)) {
+    const perMinute = measure.getElementsByTagName('per-minute')
+    if (perMinute.length && perMinute[perMinute.length - 1]) {
+      data.speed = parseFloat(
+        perMinute[perMinute.length - 1].textContent || '0'
+      )
+      break
+    }
+  }
+  return data
+}
+
+/** 解析xml */
+export const formatXMLFile = (file: File): FormatXMLResult => {
+  console.log(file)
+  const reader = new FileReader()
+  reader.onload = e => {
+    console.log(e)
+  }
+  reader.readAsText(file)
+  return {
+    xml: '',
+    speed: 0,
+    partNames: []
+  }
+}

+ 4 - 0
vite.config.ts

@@ -73,6 +73,10 @@ export default defineConfig({
         target: proxyUrl,
         changeOrigin: true
       },
+      '/api-teacher': {
+        target: proxyUrl,
+        changeOrigin: true
+      },
       '/api-website': {
         target: proxyUrl,
         changeOrigin: true

Some files were not shown because too many files changed in this diff