Browse Source

曲目编辑界面改版

yuanliang 1 year ago
parent
commit
868f5efb2a

+ 1 - 1
src/views/music-library/music-sheet/component/music-list.tsx

@@ -29,7 +29,7 @@ import {
   musicTagPage,
   musicSheetCategoriesQueryTree
 } from '../../api'
-import MusicOperation from '../modal/music-operation'
+import MusicOperation from '../modal/music-operationV2'
 import { subjectPage } from '@/views/system-manage/api'
 import MusicPreView from '../modal/musicPreView'
 import UseProject from '@views/music-library/music-sheet/modal/use-project'

+ 1802 - 0
src/views/music-library/music-sheet/modal/music-operationV2.tsx

@@ -0,0 +1,1802 @@
+import type {SelectOption} from 'naive-ui'
+import {
+  NAlert,
+  NButton,
+  NCascader,
+  NCheckbox,
+  NCheckboxGroup,
+  NForm,
+  NFormItem,
+  NFormItemGi,
+  NGi,
+  NGrid,
+  NInput,
+  NInputGroup,
+  NInputGroupLabel,
+  NInputNumber,
+  NModal,
+  NRadio,
+  NRadioGroup,
+  NSelect,
+  NSpace,
+  NSpin,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import {defineComponent, onMounted, PropType, reactive, ref} from 'vue'
+import {musicSheetCategoriesQueryTree, musicSheetDetail, musicSheetSave} from '../../api'
+import UploadFile from '@/components/upload-file'
+import styles from './index.module.less'
+import deepClone from '@/utils/deep.clone'
+import axios from 'axios'
+import {appKey, clientType, musicSheetSourceType} from '@/utils/constant'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {musicalInstrumentPage} from '@views/system-manage/subject-manage/api'
+import {subjectPage} from '@views/system-manage/api'
+import MusicSheetOwnerDialog from '@views/music-library/music-sheet/modal/musicSheetOwnerDialog'
+import {sysApplicationPage} from '@views/menu-manage/api'
+import {filterPointCategory} from '@views/teaching-manage/unit-test'
+import MusicCreateImg from './music-create-img'
+
+/**
+ * 获取指定元素下一个Note元素
+ * @param ele 指定元素
+ * @param selectors 选择器
+ */
+const getNextNote = (ele: any, selectors: any) => {
+  let index = 0
+  const parentEle = ele.closest(selectors)
+  let pointer = parentEle
+  const measure = parentEle?.closest('measure')
+  let siblingNote = null
+  // 查找到相邻的第一个note元素
+  while (!siblingNote && index < (measure?.childNodes.length || 50)) {
+    index++
+    if (pointer?.nextElementSibling?.tagName === 'note') {
+      siblingNote = pointer?.nextElementSibling
+    }
+    pointer = pointer?.nextElementSibling
+  }
+  return siblingNote
+}
+
+export const onlyVisible = (xml: any, partIndex: any) => {
+  if (!xml) return ''
+  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+  const partList =
+      xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
+  const parts = xmlParse.getElementsByTagName('part')
+  const visiblePartInfo = partList[partIndex]
+  if (visiblePartInfo) {
+    const id = visiblePartInfo.getAttribute('id')
+    Array.from(parts).forEach((part) => {
+      if (part && part.getAttribute('id') !== id) {
+        part.parentNode?.removeChild(part)
+        // 不等于第一行才添加避免重复添加
+      }
+
+      // 最后一个小节的结束线元素不在最后 调整
+      if (part && part.getAttribute('id') === id) {
+        const barlines = part.getElementsByTagName('barline')
+        const lastParent = barlines[barlines.length - 1]?.parentElement
+        if (lastParent?.lastElementChild?.tagName !== 'barline') {
+          const children: any[] = (lastParent?.children as any) || []
+          for (let el of children) {
+            if (el.tagName === 'barline') {
+              // 将结束线元素放到最后
+              lastParent?.appendChild(el)
+              break
+            }
+          }
+        }
+      }
+    })
+    Array.from(partList).forEach((part) => {
+      if (part && part.getAttribute('id') !== id) {
+        part.parentNode?.removeChild(part)
+      }
+    })
+    // 处理装饰音问题
+    const notes = xmlParse.getElementsByTagName('note')
+    const getNextvNoteDuration = (i: any) => {
+      let nextNote = notes[i + 1]
+      // 可能存在多个装饰音问题,取下一个非装饰音时值
+      for (let index = i; index < notes.length; index++) {
+        const note = notes[index]
+        if (!note.getElementsByTagName('grace')?.length) {
+          nextNote = note
+          break
+        }
+      }
+      return nextNote?.getElementsByTagName('duration')[0]
+    }
+    Array.from(notes).forEach((note, i) => {
+      const graces = note.getElementsByTagName('grace')
+      if (graces && graces.length) {
+        note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
+      }
+    })
+  }
+  return new XMLSerializer().serializeToString(xmlParse)
+}
+
+const speedInfo = {
+  'rall.': 1.333333333,
+  'poco rit.': 1.333333333,
+  'rit.': 1.333333333,
+  'molto rit.': 1.333333333,
+  'molto rall': 1.333333333,
+  molto: 1.333333333,
+  lentando: 1.333333333,
+  allargando: 1.333333333,
+  morendo: 1.333333333,
+  'accel.': 0.8,
+  calando: 2,
+  'poco accel.': 0.8,
+  'gradually slowing': 1.333333333,
+  slowing: 1.333333333,
+  slow: 1.333333333,
+  slowly: 1.333333333,
+  faster: 1.333333333
+}
+
+/**
+ * 按照xml进行减慢速度的计算
+ * @param xml 始终按照第一分谱进行减慢速度的计算
+ */
+export function getGradualLengthByXml(xml: string) {
+  const firstPartXml = onlyVisible(xml, 0)
+  const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
+  const measures = Array.from(xmlParse.querySelectorAll('measure'))
+  const notes = Array.from(xmlParse.querySelectorAll('note'))
+  const words = Array.from(xmlParse.querySelectorAll('words'))
+  const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
+
+  const eles = []
+
+  for (const ele of [...words, ...metronomes]) {
+    const note = getNextNote(ele, 'direction')
+    // console.log(ele, note)
+    if (note) {
+      const measure = note?.closest('measure')
+      const measureNotes = Array.from(measure.querySelectorAll('note'))
+
+      const noteInMeasureIndex = Array.from(measure.childNodes)
+          .filter((item: any) => item.nodeName === 'note')
+          .findIndex((item) => item === note)
+
+      let allDuration = 0
+      let leftDuration = 0
+      for (let i = 0; i < measureNotes.length; i++) {
+        const n: any = measureNotes[i]
+        const duration = +(n.querySelector('duration')?.textContent || '0')
+        allDuration += duration
+        if (i < noteInMeasureIndex) {
+          leftDuration = allDuration
+        }
+      }
+      eles.push({
+        ele,
+        index: notes.indexOf(note),
+        noteInMeasureIndex,
+        textContent: ele.textContent,
+        measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number')
+        type: ele.tagName,
+        allDuration,
+        leftDuration
+      })
+    }
+  }
+
+  // 结尾处手动插入一个音符节点
+  eles.push({
+    ele: notes[notes.length - 1],
+    index: notes.length,
+    noteInMeasureIndex: 0,
+    textContent: '',
+    type: 'metronome',
+    allDuration: 1,
+    leftDuration: 1,
+    measureIndex: measures.length
+  })
+
+  const gradualNotes: any[] = []
+  eles.sort((a, b) => a.index - b.index)
+  const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase())
+  let isLastNoteAndNotClosed = false
+  for (const ele of eles) {
+    const textContent: any = ele.textContent?.toLocaleLowerCase().trim()
+    if (ele === eles[eles.length - 1]) {
+      if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
+        isLastNoteAndNotClosed = true
+      }
+    }
+
+    const isKeyWork = keys.find((k) => {
+      const ks = k.split(' ')
+      return textContent && ks.includes(textContent)
+    })
+    if (
+        ele.type === 'metronome' ||
+        (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) ||
+        isLastNoteAndNotClosed
+    ) {
+      const indexOf = gradualNotes.findIndex((item) => item.length === 1)
+      if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
+        gradualNotes[indexOf][1] = {
+          start: ele.index,
+          measureIndex: ele.measureIndex,
+          noteInMeasureIndex: ele.noteInMeasureIndex,
+          allDuration: ele.allDuration,
+          leftDuration: ele.leftDuration,
+          type: textContent
+        }
+      }
+    }
+    if (ele.type === 'words' && isKeyWork) {
+      gradualNotes.push([
+        {
+          start: ele.index,
+          measureIndex: ele.measureIndex,
+          noteInMeasureIndex: ele.noteInMeasureIndex,
+          allDuration: ele.allDuration,
+          leftDuration: ele.leftDuration,
+          type: textContent
+        }
+      ])
+    }
+  }
+  return gradualNotes
+}
+
+export default defineComponent({
+  name: 'music-operation',
+  props: {
+    type: {
+      type: String,
+      default: 'add'
+    },
+    data: {
+      type: Object as PropType<any>,
+      default: () => {
+      }
+    },
+    tagList: {
+      type: Array as PropType<Array<SelectOption>>,
+      default: () => []
+    },
+    subjectList: {
+      type: Array as PropType<Array<SelectOption>>,
+      default: () => []
+    }
+    // musicSheetCategories: {
+    //   type: Array as PropType<Array<SelectOption>>,
+    //   default: () => []
+    // }
+  },
+  emits: ['close', 'getList'],
+
+  setup(props, {slots, attrs, emit}) {
+    const forms = reactive({
+      details: {} as any, // 曲目详情
+      graduals: {} as any, // 渐变速度
+      playMode: 'MP3', // 播放类型
+      xmlFileUrl: null, // XML
+      midiUrl: null, // mid
+      name: null, // 曲目名称
+      // musicTag: [] as any, // 曲目标签
+      composer: null, // 音乐人
+      playSpeed: null as any, // 曲谱速度
+      // showFingering: null as any, // 是否显示指法
+      // canEvaluate: null as any, // 是否评测
+      // notation: null as any, // 能否转和简谱
+      // auditVersion: null as any, // 审核版本
+      // sortNumber: null, // 排序
+      musicCover: null, // 曲谱封面
+      remark: null, // 曲谱描述
+      musicSheetSoundList: [] as any, // 原音
+      singleMusicSheetSoundList: [] as any, // 单声轨原音
+      // musicSheetCategoriesId: null,
+      status: false,
+      musicSheetType: 'SINGLE', // 曲目类型
+      sourceType: null as any, //来源类型/作者属性(PLATFORM: 平台; ORG: 机构; PERSON: 个人)
+      // userId: null, // 所属人
+      appAuditFlag: 0, // 是否审核版本
+      midiFileUrl: null, // 伴奏文件 MIDI文件(保留字段)
+      subjectIds: [] as any, // 可用声部
+      musicalInstrumentIdList: [] as any, //可用乐器
+      musicCategoryId: null, //曲目分类
+      musicSheetAccompanimentList: [] as any, //曲目伴奏
+      audioType: 'HOMEMODE', // 伴奏类型
+      isPlayBeat: true, // 是否播放节拍器
+      isUseSystemBeat: true, // 是否使用系统节拍器(0:否;1:是)
+      repeatedBeats: false, // 是否重复节拍时长
+      evaluationStandard: 'FREQUENCY', // 评分标准 节奏 AMPLITUDE 音准 FREQUENCY 分贝 DECIBELS
+      multiTracksSelection: [] as any, // 声轨
+      multiInstrumentSelection: [] as any, // 乐器
+      musicSheetExtend: {} as any, //所属人信息
+      musicImg: '', // 五线谱图片
+      musicFirstImg: '', //首调图片
+      musicJianImg: '', // 简谱固定调
+      isEvxml: false, // 是否是evxml
+      isShowFingering: true, // 是否显示指法
+    })
+    const state = reactive({
+      loading: false,
+      previewMode: false, //是否是预览模式
+      tagList: [...props.tagList] as any, // 标签列表
+      xmlFirstSpeed: null as any, // 第一个音轨速度
+      partListNames: [] as any, // 所有音轨声部列表
+      musicSheetCategories: [] as any,
+      musicSheetAccompanimentUrls: '' as any,
+      musicSheetAccompanimentUrlList: [] as any,
+      instrumentData: [],
+      instrumentList: [],
+      subjectList: [] as any,
+      showMusicSheetOwnerDialog: false, //所属人弹框
+      // musicSheetOwnerData: {}, //所属人信息
+      multiTracks: null,
+      multiInstruments: null,
+      appData: [], // 应用列表
+      ownerName: null as any, // 所属人名称描述
+
+      productOpen: false, // 是否打开自动生成图片
+      productItem: {} as any,
+      productIfameSrc: '',
+      isAutoSave: false, // 是否自动保存
+      fSongFile: null as any, // 范唱
+      bSongFile: null as any, // 伴唱
+    })
+    const gradualData = reactive({
+      list: [] as any[],
+      gradualRefs: [] as any[]
+    })
+
+    const btnLoading = ref(false)
+    const formsRef = ref()
+    const message = useMessage()
+    const dialog = useDialog()
+
+    // 提交记录
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) {
+          return
+        }
+        if (!state.isAutoSave) {
+          state.isAutoSave = true
+          state.productOpen = true
+          return
+        }
+        try {
+          //extConfigJson: {"repeatedBeats":0,"gradualTimes":{"75":"02:38:60","77":"02:43:39"}}
+          const obj = {
+            ...forms,
+            musicTag: '-1',
+            multiTracksSelection: forms.multiTracksSelection.join(','),
+            musicSheetSoundList: forms.musicSheetSoundList.filter((next: any) => {
+              return !!next.audioFileUrl && forms.multiTracksSelection.includes(next.track)
+            }),
+            musicalInstrumentIds: forms.musicalInstrumentIdList.join(','),
+            extConfigJson: JSON.stringify({
+              repeatedBeats: forms.repeatedBeats ? 1 : 0,
+              gradualTimes: forms.graduals,
+              isEvxml: forms.isEvxml ? 1 : 0,
+            }),
+            subjectIds: forms.subjectIds.join(',')
+          }
+          if (forms.audioType == 'MIDI') {
+            obj.musicSheetSoundList = []
+          }
+          btnLoading.value = true
+          if (props.type === 'add') {
+            await musicSheetSave(obj)
+            message.success('添加成功')
+          } else if (props.type === 'edit') {
+            await musicSheetSave({...obj, id: props.data.id})
+            message.success('修改成功')
+          }
+          emit('getList')
+          emit('close')
+        } catch (e) {
+          console.log(e)
+        }
+        setTimeout(() => {
+          btnLoading.value = false
+        }, 100)
+      })
+    }
+
+    // 上传XML,初始化音轨 音轨速度 乐器、声部
+    const readFileInputEventAsArrayBuffer = (file: any) => {
+      // 是否是evxml
+      // forms.isEvxml = file?.name?.includes('.evxml') ? true : false;
+      const xmlRead = new FileReader()
+      xmlRead.onload = (res) => {
+        try {
+          gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter(
+              (item: any) => item.length === 2
+          )
+        } catch (error) {
+        }
+        state.partListNames = getPartListNames(res?.target?.result as any) as any
+        parseInstrumentAndSubject(res?.target?.result as any)
+        // 这里是如果没有当前音轨就重新写
+        for (let j = 0; j < state.partListNames.length; j++) {
+          if (!forms.musicSheetSoundList[j]) {
+            forms.musicSheetSoundList.push({audioFileUrl: null, track: null})
+          }
+          forms.musicSheetSoundList[j].track = state.partListNames[j].value
+        }
+
+        // 循环添加所在音轨的原音
+        for (
+            let index = forms.musicSheetSoundList.length;
+            index < state.partListNames.length;
+            index++
+        ) {
+          const part = state.partListNames[index].value
+          const sysData = {
+            ...forms.musicSheetSoundList[0],
+            track: part
+          }
+          if (!sysData.speed) {
+            sysData.speed = state.xmlFirstSpeed
+          }
+          createSys(sysData)
+        }
+
+        if (forms.musicSheetSoundList.length == 0) {
+          forms.musicSheetSoundList.push({audioFileUrl: '', track: ''})
+        }
+      }
+      xmlRead.readAsText(file)
+    }
+
+    const containOther = (track: any) => {
+      for (let i = 0; i < state.partListNames.length; i++) {
+        if (state.partListNames[i].value == track) {
+          return true
+        }
+      }
+      return false
+    }
+
+    const parseInstrumentAndSubject = (xml: any) => {
+      if (!xml) return
+      const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+      // 乐器
+      const instrumentCodeList: any = []
+      const instrumentEle = xmlParse.getElementsByTagName('virtual-instrument')
+      for (let index = 0; index < instrumentEle.length; index++) {
+        const note = instrumentEle[index]
+        const instrumentCode = note.getElementsByTagName('virtual-name')?.[0]?.textContent || ''
+        if (instrumentCode && !instrumentCodeList.includes(instrumentCode)) {
+          instrumentCodeList.push(instrumentCode)
+        }
+      }
+      // 乐器支持多编码,暂不开放
+      // const codeIdMap = new Map<string, []>() as any
+      // state.instrumentData.forEach((data: any) => {
+      //   const codes = data.code.split(/,|,/)
+      //   codes.forEach((code: string) => {
+      //     if (codeIdMap.has(code)) {
+      //       codeIdMap.get(code).push(data.id + '')
+      //     } else {
+      //       const arr = [] as any;
+      //       arr.push(data.id + '')
+      //       codeIdMap.set(code, arr)
+      //     }
+      //     // codeIdMap.set(code, data.id + '')
+      //   })
+      // })
+      // forms.musicalInstrumentIdList = []
+      // instrumentCodeList.forEach((code: string) => {
+      //   if (codeIdMap.has(code)) {
+      //     codeIdMap.get(code).forEach((c: any) => {
+      //       forms.musicalInstrumentIdList.push(c)
+      //     })
+      //   }
+      // })
+      const codeIdMap = new Map<string, string>()
+      state.instrumentData.forEach((data: any) => {
+        codeIdMap.set(data.code, data.id + '')
+      })
+      forms.musicalInstrumentIdList = []
+      instrumentCodeList.forEach((code: string) => {
+        if (codeIdMap.has(code)) {
+          forms.musicalInstrumentIdList.push(codeIdMap.get(code))
+        }
+      })
+
+      // 声部
+      if (forms.musicalInstrumentIdList.length > 0) {
+        showBackSubject(forms.musicalInstrumentIdList)
+      }
+    }
+
+    // 获取xml中所有轨道 乐器
+    const getPartListNames = (xml: any) => {
+      if (!xml) return []
+      const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+      const partList: any =
+          xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
+      let partListNames = Array.from(partList).map((item: any) => {
+        let part = item.getElementsByTagName('part-name')?.[0]?.textContent || ''
+        // evxml没有分轨,需要手动设置一个默认的名称,用于上传原音
+        if (forms.isEvxml) {
+          part = part || 'noPartName'
+        }
+        return {
+          value: part,
+          label: part
+        }
+      })
+
+      // 处理空数据
+      if (partListNames.length === 1 && forms.details.id && !partListNames[0].value) {
+        partListNames[0] = {
+          value: forms.details.multiTracksSelection,
+          label: forms.details.multiTracksSelection
+        }
+      }
+
+      partListNames = partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
+
+      if (partListNames.length > 0) {
+        forms.musicSheetSoundList = forms.musicSheetSoundList.slice(0, partListNames.length)
+      }
+
+      state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
+      if (!forms.playSpeed) {
+        if (state.xmlFirstSpeed) {
+          forms.playSpeed = Number.parseInt(state.xmlFirstSpeed)
+        } else {
+          // 速度默认给100
+          forms.playSpeed = 100
+        }
+      }
+      // console.log('xml声轨',partListNames,state.xmlFirstSpeed)
+      return partListNames
+    }
+
+    // 判断选择的音轨是否在选中
+    const initPartsListStatus = (track: string): any => {
+      // const _names = state.partListNames.filter(
+      //   (n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON'
+      // )
+      const partListNames = deepClone(state.partListNames) || []
+      const multiTracksSelection = forms.multiTracksSelection
+      partListNames.forEach((item: any) => {
+        if (multiTracksSelection.includes(item.value)) {
+          item.disabled = true
+        } else {
+          item.disabled = false
+        }
+
+        // const index = forms.musicSheetSoundList.findIndex(
+        //   (ground: any) => item.value == ground.track
+        // )
+
+        // if (index > -1 && track == item.value) {
+        //   item.disabled = false
+        // } else {
+        //   item.disabled = true
+        // }
+      })
+      return partListNames || []
+    }
+
+    // 反显声部
+    const showBackSubject = async (musicalInstrumentIdList: []) => {
+      try {
+        const {data} = await subjectPage({
+          page: 1,
+          rows: 999,
+          musicalInstrumentIdList: musicalInstrumentIdList
+        })
+        const tempList = data.rows || []
+        tempList.forEach((item: any) => {
+          forms.subjectIds.push(item.id + '')
+        })
+      } catch {
+      }
+    }
+
+    // 添加原音
+    const createSys = (initData?: any) => {
+      forms.musicSheetSoundList.push({
+        audioFileUrl: null, // 原音
+        track: null, // 轨道
+        ...initData
+      })
+    }
+    // 删除原音
+    const removeSys = (index: number) => {
+      dialog.warning({
+        title: '提示',
+        content: `是否确认删除此原音?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          const sound = forms.musicSheetSoundList[index]
+          const track = sound.track
+          if (track) {
+            const selectIndex = forms.multiTracksSelection.indexOf(track)
+            if (selectIndex > -1) {
+              forms.multiTracksSelection.splice(selectIndex, 1)
+            }
+          } else {
+            forms.musicSheetSoundList.splice(index, 1)
+          }
+        }
+      })
+    }
+
+    const checkMultiTracks = (value: string) => {
+      if (!value) {
+        return
+      }
+      if (value === 'all') {
+        forms.multiTracksSelection = []
+        state.partListNames.forEach((next: any) => {
+          forms.multiTracksSelection.push(next.value)
+        })
+      } else if (value === 'invert') {
+        state.partListNames.forEach((next: any) => {
+          const indexOf = forms.multiTracksSelection.indexOf(next.value)
+          if (indexOf > -1) {
+            forms.multiTracksSelection.splice(indexOf, 1)
+          } else {
+            forms.multiTracksSelection.push(next.value)
+          }
+        })
+      } else if (value === 'allUncheck') {
+        forms.multiTracksSelection = []
+      }
+    }
+    const setOwnerName = () => {
+      if (forms.sourceType == 'PLATFORM') {
+        state.ownerName = ''
+        return
+      }
+      if (!forms.musicSheetExtend || !forms.sourceType || !forms.musicSheetExtend?.userId) {
+        return
+      }
+      const appId = forms.musicSheetExtend.applicationId
+      const app = state.appData.filter((next: any) => {
+        return next.id == appId
+      }) as any
+      if (app.length > 0) {
+        state.ownerName = app[0].appName
+      }
+      if (forms.sourceType == 'ORG') {
+        state.ownerName += '-' + forms.musicSheetExtend.organizationRole
+      } else if (forms.sourceType == 'PERSON') {
+        state.ownerName +=
+            '-' +
+            getMapValueByKey(forms.musicSheetExtend.clientType, new Map(Object.entries(clientType)))
+        if (forms.musicSheetExtend.userName) {
+          state.ownerName += '-' + forms.musicSheetExtend.userName
+        }
+        if (forms.musicSheetExtend.phone) {
+          state.ownerName += '(' + forms.musicSheetExtend.phone + ')'
+        }
+      }
+    }
+
+    onMounted(async () => {
+      state.loading = true
+      if (props.type === 'preview') {
+        state.previewMode = true
+      }
+      // 获取乐器信息
+      {
+        if (state.instrumentList && state.instrumentList.length > 0) {
+          return
+        }
+        try {
+          const {data} = await musicalInstrumentPage({page: 1, rows: 999})
+          const tempList = data.rows || []
+          state.instrumentData = tempList
+          tempList.forEach((item: any) => {
+            item.label = item.name
+            item.value = item.id + ''
+            item.disabled = !item.enableFlag
+          })
+          state.instrumentList = tempList
+        } catch {
+        }
+      }
+      state.subjectList = deepClone(props.subjectList)
+      state.subjectList.forEach((subject: any) => {
+        subject.disabled = !subject.enableFlag
+      })
+
+      // 初始化应用
+      {
+        const appKeys = Object.keys(appKey)
+
+        const {data} = await sysApplicationPage({page: 1, rows: 999, parentId: 0})
+        const tempList = data.rows || []
+        const filter = tempList.filter((next: any) => {
+          return appKeys.includes(next.appKey)
+        })
+        filter.forEach((item: any) => {
+          item.label = item.appName
+          item.value = item.id
+        })
+        state.appData = filter
+      }
+
+      // 获取分类信息
+      {
+        try {
+          const {data} = await musicSheetCategoriesQueryTree({enable: true})
+          state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList')
+        } catch (e) {
+        }
+      }
+
+      if (props.type === 'edit' || props.type === 'preview') {
+        const detail = props.data
+        try {
+          const {data} = await musicSheetDetail({id: detail.id})
+          forms.details = data
+          forms.playMode = data.playMode
+          forms.xmlFileUrl = data.xmlFileUrl
+          forms.midiUrl = data.midiUrl
+          forms.name = data.name
+          // forms.musicTag = data.musicTag?.split(',')
+          forms.composer = data.composer
+          forms.playSpeed = data.playSpeed
+          // forms.showFingering = Number(data.showFingering)
+          // forms.canEvaluate = Number(data.canEvaluate)
+          // forms.notation = Number(data.notation)
+          // forms.auditVersion = Number(data.auditVersion)
+          // forms.sortNumber = data.sortNumber
+          forms.musicCover = data.musicCover
+          forms.remark = data.remark
+          forms.status = data.status
+          forms.musicSheetType = data.musicSheetType || 'SINGLE'
+          forms.sourceType = data.sourceType
+          forms.appAuditFlag = data.appAuditFlag ? 1 : 0
+          forms.midiFileUrl = data.midiFileUrl
+          forms.isShowFingering = data.isShowFingering
+          forms.subjectIds = []
+          if (data.subjectIds) {
+            const subjectIds = data.subjectIds.split(',') || []
+            subjectIds.forEach((subjectId: any) => {
+              if (!forms.subjectIds.includes(subjectId)) {
+                forms.subjectIds.push(subjectId)
+              }
+            })
+          }
+          forms.musicalInstrumentIdList = data.musicalInstrumentIds
+              ? data.musicalInstrumentIds.split(',')
+              : []
+          forms.musicCategoryId = data.musicCategoryId
+          data.musicSheetAccompanimentList?.forEach((next: any) => {
+            state.musicSheetAccompanimentUrlList.push(next.audioFileUrl)
+          })
+          forms.musicSheetAccompanimentList = data.musicSheetAccompanimentList
+          forms.audioType = data.audioType
+          forms.isPlayBeat = data.isPlayBeat
+          forms.isUseSystemBeat = data.isUseSystemBeat
+          // 获取渐变 和 是否多声部
+          try {
+            const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
+            forms.graduals = extConfigJson.gradualTimes || {}
+            forms.repeatedBeats = !!extConfigJson.repeatedBeats
+            forms.isEvxml = !!extConfigJson.isEvxml
+          } catch (error) {
+          }
+          forms.evaluationStandard = data.evaluationStandard
+          forms.musicSheetExtend = data.musicSheetExtend
+
+          setOwnerName()
+          axios.get(data.xmlFileUrl).then((res: any) => {
+            if (res?.data) {
+              gradualData.list = getGradualLengthByXml(res?.data as any).filter(
+                  (item: any) => item.length === 2
+              )
+              state.partListNames = getPartListNames(res?.data as any) as any
+
+              // 初始化音轨和原音
+              forms.multiTracksSelection = data.multiTracksSelection
+                  ? data.multiTracksSelection.split(',')
+                  : []
+
+              const existSoundList = data.musicSheetSoundList ? data.musicSheetSoundList : []
+              if (existSoundList.length === 1 && existSoundList[0].track === 'P1') {
+                forms.musicSheetSoundList.push({
+                  audioFileUrl: existSoundList[0].audioFileUrl, // 原音
+                  track: state.partListNames[0].value || null // 轨道
+                })
+              } else {
+                const tracks = [] as any
+                state.partListNames.forEach((item: any) => {
+                  let audioFileUrl = null
+                  existSoundList.forEach((next: any) => {
+                    if (next.track == item.value) {
+                      audioFileUrl = next.audioFileUrl
+                    }
+                  })
+                  forms.musicSheetSoundList.push({
+                    audioFileUrl: audioFileUrl, // 原音
+                    track: item.value // 轨道
+                  })
+                  tracks.push(item.value)
+                })
+
+                // 处理没有声轨,但有原音
+                existSoundList
+                    .filter((next: any) => {
+                      return !tracks.includes(next.track)
+                    })
+                    .forEach((next: any) => {
+                      forms.musicSheetSoundList.push({
+                        audioFileUrl: next.audioFileUrl, // 原音
+                        track: next.track ? next.track : null // 轨道
+                      })
+                    })
+              }
+            }
+          })
+        } catch (error) {
+        }
+      } else {
+        // 新增只能使用启用状态的数据
+        state.subjectList = state.subjectList.filter((next: any) => {
+          return next.enableFlag == true
+        })
+        state.instrumentList = state.instrumentList.filter((next: any) => {
+          return next.enableFlag == true
+        })
+      }
+      state.loading = false
+    })
+
+    return () => (
+        <div style="background: #fff; padding-top: 12px">
+          <NSpin show={state.loading}>
+            <NForm
+                class={styles.formContainer}
+                model={forms}
+                ref={formsRef}
+                label-placement="left"
+                label-width="150"
+                disabled={state.previewMode}
+            >
+              <NAlert showIcon={false} style={{marginBottom: '12px'}}>
+                基本信息
+              </NAlert>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="曲目名称"
+                    path="name"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入曲目名称',
+                        trigger: ['input', 'blur']
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.name}
+                      placeholder="请输入曲目名称"
+                      maxlength={50}
+                      showCount
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="音乐人"
+                    path="composer"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入音乐人',
+                        trigger: ['input', 'blur']
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.composer}
+                      placeholder="请输入音乐人名称"
+                      showCount
+                      maxlength={14}
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="曲目封面"
+                    path="musicCover"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请上传曲目封面',
+                        trigger: ['input', 'blur']
+                      }
+                    ]}
+                >
+                  <UploadFile
+                      desc={'封面图'}
+                      disabled={state.previewMode}
+                      accept=".jpg,.jpeg,.png"
+                      tips="请上传大小1M以内的JPG、PNG图片"
+                      size={1}
+                      v-model:fileList={forms.musicCover}
+                      cropper
+                      bucketName="cbs"
+                      options={{
+                        autoCrop: true, //是否默认生成截图框
+                        enlarge: 2, //  图片放大倍数
+                        autoCropWidth: 200, //默框高度
+                        fixedBox: true, //是否固定截图框大认生成截图框宽度
+                        autoCropHeight: 200, //默认生成截图小 不允许改变
+                        previewsCircle: false, //预览图是否是原圆形
+                        title: '曲目封面'
+                      }}
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="曲目类型"
+                    path="musicSheetType"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择多声轨渲染',
+                        trigger: 'change'
+                      }
+                    ]}
+                >
+                  <NRadioGroup v-model:value={forms.musicSheetType}>
+                    <NRadio value={'SINGLE'}>独奏</NRadio>
+                    <NRadio value={'CONCERT'}>合奏</NRadio>
+                  </NRadioGroup>
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="作者属性"
+                    path="sourceType"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择作者属性',
+                        trigger: 'change'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      v-model:value={forms.sourceType}
+                      options={getSelectDataFromObj(musicSheetSourceType)}
+                      placeholder="请选择作者属性"
+                      onUpdateValue={() => {
+                        // 发送变化,清理选择的所属人信息
+                        forms.musicSheetExtend = {}
+                        state.ownerName = null
+                        // forms.musicSheetExtend.userId = null
+                        // forms.musicSheetExtend.userName = null
+                        // forms.musicSheetExtend.applicationId = null
+                        // forms.musicSheetExtend.organizationRoleId = null
+                      }}
+                  />
+                </NFormItemGi>
+                {forms.sourceType === 'PERSON' && (
+                    <NFormItemGi
+                        label="所属人"
+                        path="musicSheetExtend.userId"
+                        rule={[
+                          {
+                            required: true,
+                            message: '请选择曲目所属人',
+                            trigger: ['input', 'change']
+                          }
+                        ]}
+                    >
+                      <NButton
+                          disabled={state.previewMode || !forms.sourceType}
+                          type="primary"
+                          size="small"
+                          text
+                          //v-auth="orchestraSubsidyStandard/update1597887579789053953"
+                          onClick={() => {
+                            state.showMusicSheetOwnerDialog = true
+                          }}
+                      >
+                        {state.ownerName ? state.ownerName : '请选择所属人'}
+                      </NButton>
+                    </NFormItemGi>
+                )}
+                {forms.sourceType === 'ORG' && (
+                    <NFormItemGi
+                        label="所属人"
+                        path="musicSheetExtend.organizationRoleId"
+                        rule={[
+                          {
+                            required: true,
+                            message: '请选择曲目所属机构',
+                            trigger: ['input', 'change']
+                          }
+                        ]}
+                    >
+                      <NButton
+                          disabled={state.previewMode || !forms.sourceType}
+                          type="primary"
+                          size="small"
+                          text
+                          //v-auth="orchestraSubsidyStandard/update1597887579789053953"
+                          onClick={() => {
+                            state.showMusicSheetOwnerDialog = true
+                          }}
+                      >
+                        {state.ownerName ? state.ownerName : '请选择所属机构'}
+                      </NButton>
+                    </NFormItemGi>
+                )}
+              </NGrid>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="审核版本"
+                    path="appAuditFlag"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择审核版本',
+                        trigger: 'change',
+                        type: 'number'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      options={
+                        [
+                          {
+                            label: '是',
+                            value: 1
+                          },
+                          {
+                            label: '否',
+                            value: 0
+                          }
+                        ] as any
+                      }
+                      v-model:value={forms.appAuditFlag}
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="曲目分类"
+                    path="musicCategoryId"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择曲目分类',
+                        trigger: ['change']
+                      }
+                    ]}
+                >
+                  <NCascader
+                      valueField="id"
+                      labelField="name"
+                      children-field="musicSheetCategoriesList"
+                      placeholder="请选择分类"
+                      v-model:value={forms.musicCategoryId}
+                      options={state.musicSheetCategories}
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="速度"
+                    path="playSpeed"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入速度'
+                      }
+                    ]}
+                >
+                  <NInputNumber
+                      placeholder="请输入速度"
+                      v-model:value={forms.playSpeed}
+                      style="width:100%"
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NAlert showIcon={false} style={{marginBottom: '12px'}}>
+                曲目设置
+              </NAlert>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="播放模式"
+                    path="playMode"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择播放模式'
+                      }
+                    ]}
+                >
+                  <NRadioGroup
+                      v-model:value={forms.playMode}
+                      onUpdateValue={(value: string | number | boolean) => {
+                        if (value === 'MP3') {
+                          forms.playMode = 'MP3'
+                        } else {
+                          forms.playMode = 'MIDI'
+                        }
+                      }}
+                  >
+                    <NRadio value="MP3">MP3</NRadio>
+                    <NRadio value="MIDI">MID</NRadio>
+                  </NRadioGroup>
+                </NFormItemGi>
+                {forms.playMode === 'MP3' && (
+                    <NFormItemGi
+                        label="伴奏类型"
+                        path="audioType"
+                        rule={[
+                          {
+                            required: true,
+                            message: '请选择伴奏类型'
+                          }
+                        ]}
+                    >
+                      <NRadioGroup v-model:value={forms.audioType}>
+                        <NRadio value={'HOMEMODE'}>自制伴奏</NRadio>
+                        <NRadio value={'COMMON'}>普通伴奏</NRadio>
+                      </NRadioGroup>
+                    </NFormItemGi>
+                )}
+              </NGrid>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="上传XML"
+                    path="xmlFileUrl"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择上传XML',
+                        trigger: ['change', 'input']
+                      }
+                    ]}
+                >
+                  <UploadFile
+                      desc={'XML文件'}
+                      disabled={state.previewMode}
+                      size={10}
+                      key={'xmlFileUrl'}
+                      v-model:fileList={forms.xmlFileUrl}
+                      tips="仅支持上传.xml/.mxml格式文件"
+                      listType="image"
+                      accept=".xml,.mxml,.evxml"
+                      bucketName="cloud-coach"
+                      text="点击上传XML文件"
+                      onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
+                      onRemove={() => {
+                        forms.multiTracksSelection = []
+                        state.partListNames = []
+                        forms.musicSheetSoundList = []
+                        forms.musicalInstrumentIdList = []
+                        forms.subjectIds = []
+                      }}
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="重复节拍时长"
+                    path="repeatedBeats"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请选择是否重复节拍时长'
+                      }
+                    ]}
+                >
+                  <NRadioGroup v-model:value={forms.repeatedBeats}>
+                    <NRadio value={true}>是</NRadio>
+                    <NRadio value={false}>否</NRadio>
+                  </NRadioGroup>
+                </NFormItemGi>
+                <NFormItemGi
+                    label="评分标准"
+                    path="evaluationStandard"
+                    rule={[
+                      {
+                        required: true
+                      }
+                    ]}
+                >
+                  <NRadioGroup v-model:value={forms.evaluationStandard}>
+                    <NRadio value={'FREQUENCY'}>标准评测</NRadio>
+                    <NRadio value={'AMPLITUDE'}>打击乐(振幅)</NRadio>
+                    <NRadio value={'DECIBELS'}>节奏(分贝)</NRadio>
+                  </NRadioGroup>
+                </NFormItemGi>
+              </NGrid>
+
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="是否显示指法"
+                    path="isShowFingering"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择是否显示指法'
+                      }
+                    ]}
+                >
+                  <NRadioGroup v-model:value={forms.isShowFingering}>
+                    <NRadio value={true}>是</NRadio>
+                    <NRadio value={false}>否</NRadio>
+                  </NRadioGroup>
+                </NFormItemGi>
+              </NGrid>
+
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="是否播放节拍器"
+                    path="isPlayBeat"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择是否播放节拍器'
+                      }
+                    ]}
+                >
+                  <NRadioGroup v-model:value={forms.isPlayBeat}>
+                    <NRadio value={true}>是</NRadio>
+                    <NRadio value={false}>否</NRadio>
+                  </NRadioGroup>
+                </NFormItemGi>
+                {forms.isPlayBeat && (
+                    <NFormItemGi
+                        label="播放方式"
+                        path="isUseSystemBeat"
+                        rule={[
+                          {
+                            required: true,
+                            message: '请选择播放方式'
+                          }
+                        ]}
+                    >
+                      <NRadioGroup v-model:value={forms.isUseSystemBeat}>
+                        <NRadio value={true}>系统节拍器</NRadio>
+                        <NRadio value={false}>MP3节拍器</NRadio>
+                      </NRadioGroup>
+                    </NFormItemGi>
+                )}
+              </NGrid>
+              {forms.musicSheetType == 'SINGLE' && (
+                  <>
+                    <NAlert showIcon={false} style={{marginBottom: '12px'}}>
+                      演唱文件
+                    </NAlert>
+                    <NGrid cols={2}>
+                      <NFormItemGi
+                          label="上传范唱"
+                          path="xmlFileUrl"
+                          rule={[
+                            {
+                              required: false,
+                              message: '请选择上传范唱',
+                              trigger: ['change', 'input']
+                            }
+                          ]}
+                      >
+                        <UploadFile
+                            desc={'上传范唱'}
+                            disabled={state.previewMode}
+                            size={10}
+                            key={'xmlFileUrl'}
+                            v-model:fileList={state.fSongFile}
+                            tips="仅支持上传.mp3格式文件"
+                            listType="image"
+                            accept=".mp3"
+                            bucketName="cloud-coach"
+                            text="点击上传范唱文件"
+                            onRemove={() => {
+                            }}
+                        />
+                      </NFormItemGi>
+                      <NFormItemGi
+                          label="上传伴唱"
+                          path="midiFileUrl"
+                          rule={[
+                            {
+                              required: false,
+                              message: '请选择上传.MID格式文件'
+                            }
+                          ]}
+                      >
+                        <UploadFile
+                            desc={'上传伴唱'}
+                            disabled={state.previewMode}
+                            size={10}
+                            v-model:fileList={state.bSongFile}
+                            tips="仅支持上传.mp3格式文件"
+                            listType="image"
+                            accept=".mp3"
+                            bucketName="cloud-coach"
+                            text="点击上传伴唱文件"
+                        />
+                      </NFormItemGi>
+                    </NGrid>
+                  </>
+              )}
+              <NAlert showIcon={false} style={{marginBottom: '12px'}}>
+                演奏文件
+              </NAlert>
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="可用声部"
+                    path="subjectIds"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择可用声部',
+                        trigger: 'change',
+                        type: 'array'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      v-model:value={forms.subjectIds}
+                      options={state.subjectList}
+                      multiple
+                      filterable
+                      clearable
+                      placeholder="请选择可用声部"
+                      maxTagCount={2}
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="可用乐器"
+                    path="musicalInstrumentIdList"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择可用乐器',
+                        trigger: 'change',
+                        type: 'array'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      placeholder="请选择可用乐器"
+                      options={state.instrumentList}
+                      v-model:value={forms.musicalInstrumentIdList}
+                      clearable
+                      multiple
+                      maxTagCount={2}
+                      onUpdateValue={async (value: any) => {
+                        forms.singleMusicSheetSoundList = [];
+                        state.instrumentList.forEach((instrument: any) => {
+                          if (value.includes(instrument.value)) {
+                            forms.singleMusicSheetSoundList.push({
+                              'label': instrument.name,
+                              'value': instrument.id + '',
+                              'file': null
+                            });
+                          }
+                        })
+                      }}
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={2}>
+                {forms.playMode === 'MP3' && (
+                    <NFormItemGi
+                        label="上传伴奏"
+                        path="musicSheetAccompanimentList"
+                        rule={[
+                          {
+                            required: false,
+                            message: '请选择上传.mp3'
+                          }
+                        ]}
+                    >
+                      <UploadFile
+                          disabled={state.previewMode}
+                          size={10}
+                          v-model:imageList={state.musicSheetAccompanimentUrlList}
+                          tips="仅支持上传.mp3格式文件"
+                          listType="image"
+                          accept=".mp3"
+                          bucketName="cloud-coach"
+                          text="点击上传伴奏文件"
+                          max={10}
+                          desc={'上传伴奏文件'}
+                          onUpload:success={(file) => {
+                            state.musicSheetAccompanimentUrls = [
+                              state.musicSheetAccompanimentUrls,
+                              file.url
+                            ]
+                                .filter(Boolean)
+                                .join(',')
+                            state.musicSheetAccompanimentUrlList = state.musicSheetAccompanimentUrls
+                                ?.split(',')
+                                .filter(Boolean)
+                            forms.musicSheetAccompanimentList = []
+                            for (let i = 0; i < state.musicSheetAccompanimentUrlList.length; i++) {
+                              forms.musicSheetAccompanimentList.push({
+                                audioFileUrl: state.musicSheetAccompanimentUrlList[i],
+                                sortNumber: i + 1
+                              })
+                            }
+                          }}
+                          onRemove={() => {
+                            state.musicSheetAccompanimentUrlList = []
+                            state.musicSheetAccompanimentUrls = ''
+                          }}
+                          // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
+                          multiple={true}
+                      />
+                    </NFormItemGi>
+                )}
+              </NGrid>
+
+              {/*渐变速*/}
+              {!!gradualData.list.length && (
+                  <>
+                    <NAlert showIcon={false} type="info">
+                      识别到共1处渐变速度,请输入Dorico对应小节时间信息
+                    </NAlert>
+                    <NFormItem label="rit." required style={{marginTop: '10px'}}>
+                      <NSpace vertical>
+                        {gradualData.list.map((n: any, ni: number) => (
+                            <NInputGroup>
+                              <NFormItem
+                                  path={`graduals.${n[0].measureIndex}`}
+                                  rule={[
+                                    {required: true, message: '请输入合奏曲目时间'},
+                                    {
+                                      pattern: /^((\d{2}):?){2,3}$/,
+                                      message: '请输入正确的曲目时间',
+                                      trigger: 'blur'
+                                    }
+                                  ]}
+                              >
+                                <NInputGroup>
+                                  <NInputGroupLabel>{n[0].measureIndex}小节开始</NInputGroupLabel>
+                                  <NInput
+                                      placeholder="00:00:00"
+                                      v-model:value={forms.graduals[n[0].measureIndex]}
+                                  ></NInput>
+                                </NInputGroup>
+                              </NFormItem>
+                              <div style={{lineHeight: '30px', padding: '0 4px'}}>~</div>
+                              <NFormItem
+                                  path={`graduals.${n[1].measureIndex}`}
+                                  rule={[
+                                    {required: true, message: '请输入合奏曲目时间'},
+                                    {
+                                      pattern: /^((\d{2}):?){2,3}$/,
+                                      message: '请输入正确的曲目时间',
+                                      trigger: 'blur'
+                                    }
+                                  ]}
+                              >
+                                <NInputGroup>
+                                  <NInput
+                                      placeholder="00:00:00"
+                                      v-model:value={forms.graduals[n[1].measureIndex]}
+                                  ></NInput>
+                                  <NInputGroupLabel>{n[1].measureIndex}小节结束</NInputGroupLabel>
+                                </NInputGroup>
+                              </NFormItem>
+                            </NInputGroup>
+                        ))}
+                      </NSpace>
+                    </NFormItem>
+                  </>
+              )}
+              {/*渲染声轨*/}
+              {/*单曲:从可用乐器数据渲染*/}
+              {/*合奏:从xml中解析*/}
+              {forms.musicSheetType == 'SINGLE' && (
+                  <>
+                    <NGrid cols={1}>
+                      <NFormItemGi
+                          label={'页面渲染声轨'}
+                          path="multiInstrumentSelection"
+                          rule={[
+                            {
+                              required: true,
+                              message: `页面渲染声轨`,
+                              trigger: 'change',
+                              type: 'array'
+                            }
+                          ]}
+                      >
+                        <NCheckboxGroup v-model:value={forms.multiInstrumentSelection}
+                                        onUpdateValue={(value: any) => {
+                                          forms.multiInstrumentSelection = value
+                                        }}
+                        >
+                          <NGrid yGap={2} cols={6}>
+                            {forms.singleMusicSheetSoundList.map((item: any) => (
+                                <NGi>
+                                  <NCheckbox value={item.value} label={item.label}/>
+                                </NGi>
+                            ))}
+                          </NGrid>
+                        </NCheckboxGroup>
+                      </NFormItemGi>
+                    </NGrid>
+                    {forms.singleMusicSheetSoundList.map((item: any, index: any) => {
+                      return (
+                          <>
+                            <NGrid cols={1}>
+                              {forms.multiInstrumentSelection.includes(item.value) && (
+                                  <NFormItemGi
+                                      span={12}
+                                      label={item.label}
+                                      path={`singleInstrumentList[${index}].file`}
+                                      rule={[
+                                        {
+                                          required: true,
+                                          message: `请上传乐器演奏文件`
+                                        }
+                                      ]}
+                                  >
+                                    <UploadFile
+                                        desc={'乐器演奏文件'}
+                                        disabled={state.previewMode}
+                                        size={100}
+                                        v-model:fileList={item.file}
+                                        tips="仅支持上传.mp3格式文件"
+                                        listType="image"
+                                        accept=".mp3"
+                                        text={"点击上传MP3文件"}
+                                        bucketName="cloud-coach"
+                                    />
+                                  </NFormItemGi>
+                              )}
+                            </NGrid>
+                          </>
+                      )
+                    })
+                    }
+
+                  </>
+              )}
+
+              {forms.musicSheetType == 'CONCERT' && (
+                  <>
+                    <NGrid cols={1}>
+                      <NFormItemGi
+                          label={'用户可切换声轨'}
+                          path="multiTracksSelection"
+                          rule={[
+                            {
+                              required: true,
+                              message: `请选择用户可切换声轨'
+                              }`,
+                              trigger: 'change',
+                              type: 'array'
+                            }
+                          ]}
+                      >
+                        <NGrid style="padding-top: 4px;">
+                          <NGi span={24}>
+                            <NRadioGroup
+                                v-model:value={state.multiTracks}
+                                onUpdateValue={(value) => {
+                                  checkMultiTracks(value)
+                                }}
+                            >
+                              <NRadio value={'all'}>全选</NRadio>
+                              <NRadio value={'allUncheck'}>重置</NRadio>
+                              <NRadio value={'invert'}>反选</NRadio>
+                            </NRadioGroup>
+                          </NGi>
+                          {state.partListNames && state.partListNames.length > 0 && (
+                              <NGi span={24} style={'margin-top:5px'}>
+                                <NFormItemGi
+                                    label=""
+                                    path="multiTracksSelection"
+                                    rule={[
+                                      {
+                                        required: false
+                                      }
+                                    ]}
+                                >
+                                  <NCheckboxGroup v-model:value={forms.multiTracksSelection}>
+                                    <NGrid yGap={2} cols={4}>
+                                      {state.partListNames.map((item: any) => (
+                                          <NGi>
+                                            <NCheckbox value={item.value} label={item.label}/>
+                                          </NGi>
+                                      ))}
+                                    </NGrid>
+                                  </NCheckboxGroup>
+                                </NFormItemGi>
+                              </NGi>
+                          )}
+                        </NGrid>
+                      </NFormItemGi>
+                    </NGrid>
+                    {/* 只有播放类型为mp3时才会有原音 */}
+                    {forms.playMode === 'MP3' && forms.musicSheetSoundList.length > 0 && (
+                        <>
+                          {forms.musicSheetSoundList.map((item: any, index: number) => {
+                            return (
+                                <>
+                                  {(!containOther(item.track) ||
+                                      (item.track?.toLocaleUpperCase?.() != 'COMMON' &&
+                                          forms.multiTracksSelection.includes(item.track))) && (
+                                      <NGrid
+                                          class={styles.audioSection}
+                                          // v-show={forms.multiTracksSelection.indexOf(item.track) > -1}
+                                      >
+                                        <NFormItemGi
+                                            span={12}
+                                            label="原音"
+                                            path={`musicSheetSoundList[${index}].audioFileUrl`}
+                                            rule={[
+                                              {
+                                                // required: forms.multiTracksSelection.indexOf(forms.musicSheetSoundList[index].audioFileUrl) > -1,
+                                                required: true,
+                                                message: `请上传${
+                                                    item.track ? item.track + '的' : '第' + (index + 1) + '个'
+                                                }原音`
+                                              }
+                                            ]}
+                                        >
+                                          <UploadFile
+                                              desc={'原音文件'}
+                                              disabled={state.previewMode}
+                                              size={100}
+                                              v-model:fileList={item.audioFileUrl}
+                                              tips="仅支持上传.mp3格式文件"
+                                              listType="image"
+                                              accept=".mp3"
+                                              bucketName="cloud-coach"
+                                          />
+                                        </NFormItemGi>
+                                        {state.partListNames.length > 0 && (
+                                            <NFormItemGi
+                                                span={12}
+                                                label="所属轨道"
+                                                path={`musicSheetSoundList[${index}].track`}
+                                                rule={[
+                                                  {
+                                                    required: true,
+                                                    message: '请选择所属轨道'
+                                                  }
+                                                ]}
+                                            >
+                                              <NSelect
+                                                  placeholder="请选择所属轨道"
+                                                  value={item.track}
+                                                  options={initPartsListStatus(item.track)}
+                                                  onUpdateValue={(value: any) => {
+                                                    const track = item.track
+
+                                                    if (track) {
+                                                      // 声轨交换
+                                                      forms.musicSheetSoundList.forEach((next: any) => {
+                                                        if (next.track == value) {
+                                                          next.track = track
+                                                        }
+                                                      })
+
+                                                      const index = forms.multiTracksSelection.indexOf(item.track)
+                                                      forms.multiTracksSelection.splice(index, 1)
+                                                    } else {
+                                                      forms.musicSheetSoundList = forms.musicSheetSoundList.filter(
+                                                          (next: any) => {
+                                                            return next.track != value
+                                                          }
+                                                      )
+                                                    }
+
+                                                    if (value && !forms.multiTracksSelection.includes(value)) {
+                                                      forms.multiTracksSelection.push(value)
+                                                    }
+                                                    item.track = value
+                                                  }}
+                                              />
+                                            </NFormItemGi>
+                                        )}
+                                        <NGi class={styles.btnRemove}>
+                                          <NButton
+                                              type="primary"
+                                              text
+                                              // disabled={forms.musicSheetSoundList.length === 1}
+                                              onClick={() => removeSys(index)}
+                                          >
+                                            删除
+                                          </NButton>
+                                        </NGi>
+                                      </NGrid>
+                                  )}
+                                </>
+                            )
+                          })}
+                        </>
+                    )}
+                  </>
+              )}
+
+            </NForm>
+          </NSpin>
+          {props.type !== 'preview' && (
+              <NSpace justify="end" style="padding-top:12px">
+                <NButton type="default" onClick={() => emit('close')}>
+                  取消
+                </NButton>
+                <NButton
+                    type="primary"
+                    onClick={() => onSubmit()}
+                    loading={btnLoading.value}
+                    disabled={btnLoading.value}
+                >
+                  确认
+                </NButton>
+              </NSpace>
+          )}
+
+          <NModal
+              v-model:show={state.showMusicSheetOwnerDialog}
+              preset="dialog"
+              showIcon={false}
+              maskClosable={false}
+              title="所属人"
+              style={{width: '800px'}}
+          >
+            <MusicSheetOwnerDialog
+                musicSheetExtend={forms.musicSheetExtend}
+                sourceType={forms.sourceType}
+                appData={state.appData}
+                onClose={() => {
+                  state.showMusicSheetOwnerDialog = false
+                }}
+                onChoseMusicSheetOwnerData={(musicSheetOwnerData) => {
+                  forms.musicSheetExtend = {
+                    ...musicSheetOwnerData
+                  }
+                  setOwnerName()
+                }}
+            />
+          </NModal>
+
+          <NModal
+              class={styles.productModal}
+              title="自动生成曲谱图片"
+              v-model:show={state.productOpen}
+              preset="dialog"
+              closeOnEsc={false}
+              maskClosable={false}
+              showIcon={false}
+          >
+            <MusicCreateImg
+                xmlFileUrl={forms.xmlFileUrl || ''}
+                onClose={() => (state.productOpen = false)}
+                onConfirm={async (item: any) => {
+                  // 保存
+                  try {
+                    forms.musicImg = item.musicImg
+                    forms.musicFirstImg = item.musicFirstImg
+                    forms.musicJianImg = item.musicJianImg
+                    onSubmit()
+                  } catch (e: any) {
+                    //
+                    console.log(e, 'e')
+                  }
+                  setTimeout(() => {
+                    state.isAutoSave = false
+                  }, 50)
+                }}
+            />
+          </NModal>
+        </div>
+    )
+  }
+})