music-operation.tsx 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849
  1. import type { SelectOption } from 'naive-ui'
  2. import {
  3. NAlert,
  4. NButton,
  5. NCascader,
  6. NCheckbox,
  7. NCheckboxGroup,
  8. NForm,
  9. NFormItem,
  10. NFormItemGi,
  11. NGi,
  12. NGrid,
  13. NInput,
  14. NInputGroup,
  15. NInputGroupLabel,
  16. NInputNumber,
  17. NModal,
  18. NRadio,
  19. NRadioGroup,
  20. NSelect,
  21. NSpace,
  22. NSpin,
  23. useDialog,
  24. useMessage
  25. } from 'naive-ui'
  26. import { defineComponent, nextTick, onMounted, PropType, reactive, ref } from 'vue'
  27. import { musicSheetCategoriesQueryTree, musicSheetDetail, musicSheetSave } from '../../api'
  28. import UploadFile from '@/components/upload-file'
  29. import styles from './index.module.less'
  30. import deepClone from '@/utils/deep.clone'
  31. import axios from 'axios'
  32. import { appKey, clientType, musicSheetSourceType, musicSheetType } from '@/utils/constant'
  33. import { getMapValueByKey, getSelectDataFromObj } from '@/utils/objectUtil'
  34. import { musicalInstrumentPage } from '@views/system-manage/subject-manage/api'
  35. import { subjectPage } from '@views/system-manage/api'
  36. import MusicSheetOwnerDialog from '@views/music-library/music-sheet/modal/musicSheetOwnerDialog'
  37. import { sysApplicationPage } from '@views/menu-manage/api'
  38. import { filterPointCategory } from '@views/teaching-manage/unit-test'
  39. import MusicCreateImg from './music-create-img'
  40. import { onUpdated } from 'vue-demi'
  41. /**
  42. * 获取指定元素下一个Note元素
  43. * @param ele 指定元素
  44. * @param selectors 选择器
  45. */
  46. const getNextNote = (ele: any, selectors: any) => {
  47. let index = 0
  48. const parentEle = ele.closest(selectors)
  49. let pointer = parentEle
  50. const measure = parentEle?.closest('measure')
  51. let siblingNote = null
  52. // 查找到相邻的第一个note元素
  53. while (!siblingNote && index < (measure?.childNodes.length || 50)) {
  54. index++
  55. if (pointer?.nextElementSibling?.tagName === 'note') {
  56. siblingNote = pointer?.nextElementSibling
  57. }
  58. pointer = pointer?.nextElementSibling
  59. }
  60. return siblingNote
  61. }
  62. export const onlyVisible = (xml: any, partIndex: any) => {
  63. if (!xml) return ''
  64. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  65. const partList =
  66. xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  67. const parts = xmlParse.getElementsByTagName('part')
  68. const visiblePartInfo = partList[partIndex]
  69. if (visiblePartInfo) {
  70. const id = visiblePartInfo.getAttribute('id')
  71. Array.from(parts).forEach((part) => {
  72. if (part && part.getAttribute('id') !== id) {
  73. part.parentNode?.removeChild(part)
  74. // 不等于第一行才添加避免重复添加
  75. }
  76. // 最后一个小节的结束线元素不在最后 调整
  77. if (part && part.getAttribute('id') === id) {
  78. const barlines = part.getElementsByTagName('barline')
  79. const lastParent = barlines[barlines.length - 1]?.parentElement
  80. if (lastParent?.lastElementChild?.tagName !== 'barline') {
  81. const children: any[] = (lastParent?.children as any) || []
  82. for (let el of children) {
  83. if (el.tagName === 'barline') {
  84. // 将结束线元素放到最后
  85. lastParent?.appendChild(el)
  86. break
  87. }
  88. }
  89. }
  90. }
  91. })
  92. Array.from(partList).forEach((part) => {
  93. if (part && part.getAttribute('id') !== id) {
  94. part.parentNode?.removeChild(part)
  95. }
  96. })
  97. // 处理装饰音问题
  98. const notes = xmlParse.getElementsByTagName('note')
  99. const getNextvNoteDuration = (i: any) => {
  100. let nextNote = notes[i + 1]
  101. // 可能存在多个装饰音问题,取下一个非装饰音时值
  102. for (let index = i; index < notes.length; index++) {
  103. const note = notes[index]
  104. if (!note.getElementsByTagName('grace')?.length) {
  105. nextNote = note
  106. break
  107. }
  108. }
  109. return nextNote?.getElementsByTagName('duration')[0]
  110. }
  111. Array.from(notes).forEach((note, i) => {
  112. const graces = note.getElementsByTagName('grace')
  113. if (graces && graces.length) {
  114. note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
  115. }
  116. })
  117. }
  118. return new XMLSerializer().serializeToString(xmlParse)
  119. }
  120. const speedInfo = {
  121. 'rall.': 1.333333333,
  122. 'poco rit.': 1.333333333,
  123. 'rit.': 1.333333333,
  124. 'molto rit.': 1.333333333,
  125. 'molto rall': 1.333333333,
  126. molto: 1.333333333,
  127. lentando: 1.333333333,
  128. allargando: 1.333333333,
  129. morendo: 1.333333333,
  130. 'accel.': 0.8,
  131. calando: 2,
  132. 'poco accel.': 0.8,
  133. 'gradually slowing': 1.333333333,
  134. slowing: 1.333333333,
  135. slow: 1.333333333,
  136. slowly: 1.333333333,
  137. faster: 1.333333333,
  138. "molto allargando": 1.333333333
  139. }
  140. /**
  141. * 按照xml进行减慢速度的计算
  142. * @param xml 始终按照第一分谱进行减慢速度的计算
  143. */
  144. export function getGradualLengthByXml(xml: string) {
  145. const firstPartXml = onlyVisible(xml, 0)
  146. const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
  147. const measures = Array.from(xmlParse.querySelectorAll('measure'))
  148. const notes = Array.from(xmlParse.querySelectorAll('note'))
  149. const words = Array.from(xmlParse.querySelectorAll('words'))
  150. const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
  151. const eles = []
  152. for (const ele of [...words, ...metronomes]) {
  153. const note = getNextNote(ele, 'direction')
  154. // console.log(ele, note)
  155. if (note) {
  156. const measure = note?.closest('measure')
  157. const measureNotes = Array.from(measure.querySelectorAll('note'))
  158. const noteInMeasureIndex = Array.from(measure.childNodes)
  159. .filter((item: any) => item.nodeName === 'note')
  160. .findIndex((item) => item === note)
  161. let allDuration = 0
  162. let leftDuration = 0
  163. for (let i = 0; i < measureNotes.length; i++) {
  164. const n: any = measureNotes[i]
  165. const duration = +(n.querySelector('duration')?.textContent || '0')
  166. allDuration += duration
  167. if (i < noteInMeasureIndex) {
  168. leftDuration = allDuration
  169. }
  170. }
  171. eles.push({
  172. ele,
  173. index: notes.indexOf(note),
  174. noteInMeasureIndex,
  175. textContent: ele.textContent,
  176. measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number')
  177. // measureIndex: measure?.getAttribute('number')
  178. // ? Number(measure?.getAttribute('number'))
  179. // : measures.indexOf(measure),
  180. type: ele.tagName,
  181. allDuration,
  182. leftDuration
  183. })
  184. }
  185. }
  186. // 结尾处手动插入一个音符节点
  187. eles.push({
  188. ele: notes[notes.length - 1],
  189. index: notes.length,
  190. noteInMeasureIndex: 0,
  191. textContent: '',
  192. type: 'metronome',
  193. allDuration: 1,
  194. leftDuration: 1,
  195. measureIndex: measures.length
  196. })
  197. const gradualNotes: any[] = []
  198. eles.sort((a, b) => a.index - b.index)
  199. const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase())
  200. let isLastNoteAndNotClosed = false
  201. for (const ele of eles) {
  202. const textContent: any = ele.textContent?.toLocaleLowerCase().trim()
  203. if (ele === eles[eles.length - 1]) {
  204. if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
  205. isLastNoteAndNotClosed = true
  206. }
  207. }
  208. const isKeyWork = keys.find((k) => {
  209. const ks = k.split(' ')
  210. return textContent && ks.includes(textContent) || k === textContent
  211. })
  212. if (
  213. ele.type === 'metronome' ||
  214. (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) ||
  215. isLastNoteAndNotClosed
  216. ) {
  217. const indexOf = gradualNotes.findIndex((item) => item.length === 1)
  218. if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
  219. gradualNotes[indexOf][1] = {
  220. start: ele.index,
  221. measureIndex: ele.measureIndex,
  222. noteInMeasureIndex: ele.noteInMeasureIndex,
  223. allDuration: ele.allDuration,
  224. leftDuration: ele.leftDuration,
  225. type: textContent
  226. }
  227. }
  228. }
  229. if (ele.type === 'words' && isKeyWork) {
  230. gradualNotes.push([
  231. {
  232. start: ele.index,
  233. measureIndex: ele.measureIndex,
  234. noteInMeasureIndex: ele.noteInMeasureIndex,
  235. allDuration: ele.allDuration,
  236. leftDuration: ele.leftDuration,
  237. type: textContent
  238. }
  239. ])
  240. }
  241. }
  242. return gradualNotes
  243. }
  244. export default defineComponent({
  245. name: 'music-operation',
  246. props: {
  247. type: {
  248. type: String,
  249. default: 'add'
  250. },
  251. data: {
  252. type: Object as PropType<any>,
  253. default: () => {}
  254. },
  255. tagList: {
  256. type: Array as PropType<Array<SelectOption>>,
  257. default: () => []
  258. },
  259. subjectList: {
  260. type: Array as PropType<Array<SelectOption>>,
  261. default: () => []
  262. }
  263. // musicSheetCategories: {
  264. // type: Array as PropType<Array<SelectOption>>,
  265. // default: () => []
  266. // }
  267. },
  268. emits: ['close', 'getList'],
  269. setup(props, { slots, attrs, emit }) {
  270. const forms = reactive({
  271. details: {} as any, // 曲目详情
  272. graduals: {} as any, // 渐变速度
  273. playMode: 'MP3', // 播放类型
  274. xmlFileUrl: null, // XML
  275. midiUrl: null, // mid
  276. name: null, // 曲目名称
  277. // musicTag: [] as any, // 曲目标签
  278. composer: null, // 音乐人
  279. playSpeed: null as any, // 曲谱速度
  280. // showFingering: null as any, // 是否显示指法
  281. // canEvaluate: null as any, // 是否评测
  282. // notation: null as any, // 能否转和简谱
  283. // auditVersion: null as any, // 审核版本
  284. // sortNumber: null, // 排序
  285. musicCover: null, // 曲谱封面
  286. remark: null, // 曲谱描述
  287. musicSheetSoundList: [] as any, // 原音
  288. // musicSheetCategoriesId: null,
  289. status: false,
  290. musicSheetType: 'CONCERT', // 曲目类型
  291. sourceType: 'PLATFORM' as any, //来源类型/作者属性(PLATFORM: 平台; ORG: 机构; PERSON: 个人)
  292. // userId: null, // 所属人
  293. appAuditFlag: 0, // 是否审核版本
  294. midiFileUrl: null, // 伴奏文件 MIDI文件(保留字段)
  295. subjectIds: [] as any, // 可用声部
  296. musicalInstrumentIdList: [] as any, //可用乐器
  297. musicCategoryId: null, //曲目分类
  298. musicSheetAccompanimentList: [] as any, //曲目伴奏
  299. audioType: 'HOMEMODE', // 伴奏类型
  300. isPlayBeat: true, // 是否播放节拍器
  301. isUseSystemBeat: true, // 是否使用系统节拍器(0:否;1:是)
  302. repeatedBeats: false, // 是否重复节拍时长
  303. evaluationStandard: 'FREQUENCY', // 评分标准 节奏 AMPLITUDE 音准 FREQUENCY 分贝 DECIBELS
  304. multiTracksSelection: [] as any, // 声轨
  305. musicSheetExtend: {} as any, //所属人信息
  306. musicImg: '', // 五线谱图片
  307. musicFirstImg: '', //首调图片
  308. musicJianImg: '', // 简谱固定调
  309. isEvxml: false, // 是否是evxml
  310. isShowFingering: true // 是否显示指法
  311. })
  312. const state = reactive({
  313. loading: false,
  314. previewMode: false, //是否是预览模式
  315. tagList: [...props.tagList] as any, // 标签列表
  316. xmlFirstSpeed: null as any, // 第一个音轨速度
  317. partListNames: [] as any, // 所有音轨声部列表
  318. musicSheetCategories: [] as any,
  319. musicSheetAccompanimentUrls: '' as any,
  320. musicSheetAccompanimentUrlList: [] as any,
  321. instrumentData: [],
  322. instrumentList: [],
  323. subjectList: [] as any,
  324. showMusicSheetOwnerDialog: false, //所属人弹框
  325. // musicSheetOwnerData: {}, //所属人信息
  326. multiTracks: null as any,
  327. appData: [], // 应用列表
  328. ownerName: null as any, // 所属人名称描述
  329. productOpen: false, // 是否打开自动生成图片
  330. productItem: {} as any,
  331. productIfameSrc: '',
  332. isAutoSave: false // 是否自动保存
  333. })
  334. const gradualData = reactive({
  335. list: [] as any[],
  336. gradualRefs: [] as any[]
  337. })
  338. const btnLoading = ref(false)
  339. const formsRef = ref()
  340. const message = useMessage()
  341. const dialog = useDialog()
  342. // 提交记录
  343. const onSubmit = async () => {
  344. formsRef.value.validate(async (error: any) => {
  345. if (error) {
  346. return
  347. }
  348. if (!state.isAutoSave) {
  349. state.isAutoSave = true
  350. state.productOpen = true
  351. return
  352. }
  353. try {
  354. //extConfigJson: {"repeatedBeats":0,"gradualTimes":{"75":"02:38:60","77":"02:43:39"}}
  355. const obj = {
  356. ...forms,
  357. musicSheetExtend: forms.sourceType == 'PLATFORM' ? null : forms.musicSheetExtend,
  358. musicTag: '-1',
  359. multiTracksSelection: forms.multiTracksSelection.join(','),
  360. musicSheetSoundList: forms.musicSheetSoundList.filter((next: any) => {
  361. return (
  362. !!next.audioFileUrl &&
  363. (!next.track || forms.multiTracksSelection.includes(next.track))
  364. )
  365. }),
  366. musicalInstrumentIds: forms.musicalInstrumentIdList.join(','),
  367. extConfigJson: JSON.stringify({
  368. repeatedBeats: forms.repeatedBeats ? 1 : 0,
  369. gradualTimes: forms.graduals,
  370. isEvxml: forms.isEvxml ? 1 : 0
  371. }),
  372. subjectIds: forms.subjectIds.join(',')
  373. }
  374. if (forms.audioType == 'MIDI') {
  375. obj.musicSheetSoundList = []
  376. }
  377. btnLoading.value = true
  378. if (props.type === 'add') {
  379. await musicSheetSave(obj)
  380. message.success('添加成功')
  381. } else if (props.type === 'edit') {
  382. await musicSheetSave({ ...obj, id: props.data.id })
  383. message.success('修改成功')
  384. }
  385. emit('getList')
  386. emit('close')
  387. } catch (e) {
  388. console.log(e)
  389. }
  390. setTimeout(() => {
  391. btnLoading.value = false
  392. }, 100)
  393. })
  394. }
  395. // 上传XML,初始化音轨 音轨速度 乐器、声部
  396. const readFileInputEventAsArrayBuffer = (file: any) => {
  397. // 是否是evxml
  398. // forms.isEvxml = file?.name?.includes('.evxml') ? true : false;
  399. const xmlRead = new FileReader()
  400. xmlRead.onload = (res) => {
  401. try {
  402. gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter(
  403. (item: any) => item.length === 2
  404. )
  405. } catch (error) {}
  406. forms.musicSheetSoundList = forms.musicSheetSoundList.filter((item: any) => {
  407. return (
  408. !item.track ||
  409. !containOther(item.track) ||
  410. (item.track?.toLocaleUpperCase?.() != 'COMMON' &&
  411. forms.multiTracksSelection.includes(item.track))
  412. )
  413. })
  414. state.partListNames = getPartListNames(res?.target?.result as any) as any
  415. parseInstrumentAndSubject(res?.target?.result as any)
  416. // 这里是如果没有当前音轨就重新写
  417. let map = new Map<String, String>()
  418. for (let i = 0; i < forms.musicSheetSoundList.length; i++) {
  419. let track = forms.musicSheetSoundList[i].track
  420. if (track) {
  421. map.set(track, forms.musicSheetSoundList[i])
  422. }
  423. }
  424. let newMusicSheetSoundList = []
  425. let tracks = [] as any
  426. for (let j = 0; j < state.partListNames.length; j++) {
  427. let track = state.partListNames[j].value
  428. if (map.has(track)) {
  429. newMusicSheetSoundList.push(map.get(track))
  430. } else {
  431. newMusicSheetSoundList.push({
  432. audioFileUrl: null,
  433. track: track,
  434. musicalInstrumentId: null
  435. })
  436. }
  437. tracks.push(track)
  438. if (!forms.multiTracksSelection.includes(track)) {
  439. forms.multiTracksSelection.push(track)
  440. }
  441. }
  442. for (let i = 0; i < forms.musicSheetSoundList.length; i++) {
  443. let track = forms.musicSheetSoundList[i].track
  444. if (!track || !tracks.includes(track)) {
  445. forms.musicSheetSoundList[i].track = null
  446. newMusicSheetSoundList.push(forms.musicSheetSoundList[i])
  447. }
  448. }
  449. forms.musicSheetSoundList = newMusicSheetSoundList
  450. forms.multiTracksSelection = forms.multiTracksSelection.filter((track: any) => {
  451. return tracks.includes(track)
  452. })
  453. // 全选选中
  454. state.multiTracks = 'all'
  455. // 循环添加所在音轨的原音
  456. // for (let index = forms.musicSheetSoundList.length; index < state.partListNames.length; index++) {
  457. // const part = state.partListNames[index].value
  458. // const sysData = {
  459. // ...forms.musicSheetSoundList[0],
  460. // track: part
  461. // }
  462. // if (!sysData.speed) {
  463. // sysData.speed = state.xmlFirstSpeed
  464. // }
  465. // createSys(sysData)
  466. // }
  467. if (forms.musicSheetSoundList.length == 0) {
  468. forms.musicSheetSoundList.push({ audioFileUrl: '', track: '' })
  469. }
  470. }
  471. xmlRead.readAsText(file)
  472. }
  473. const containOther = (track: any) => {
  474. for (let i = 0; i < state.partListNames.length; i++) {
  475. if (state.partListNames[i].value == track) {
  476. return true
  477. }
  478. }
  479. return false
  480. }
  481. const validSoundNum = () => {
  482. return forms.musicSheetSoundList.filter((item: any) => {
  483. return (
  484. !item.track ||
  485. !containOther(item.track) ||
  486. (item.track?.toLocaleUpperCase?.() != 'COMMON' &&
  487. forms.multiTracksSelection.includes(item.track))
  488. )
  489. }).length
  490. }
  491. const parseInstrumentAndSubject = (xml: any) => {
  492. if (!xml) return
  493. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  494. // 乐器
  495. const instrumentCodeList: any = []
  496. // const instrumentEle = xmlParse.getElementsByTagName('score-part')
  497. const instrumentEle: any =
  498. xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  499. for (let index = 0; index < instrumentEle.length; index++) {
  500. const note = instrumentEle[index]
  501. let instrumentCode = note.getElementsByTagName('part-name')?.[0]?.textContent || ''
  502. instrumentCode = instrumentCode.toLocaleLowerCase().trim()
  503. if (instrumentCode && !instrumentCodeList.includes(instrumentCode)) {
  504. instrumentCodeList.push(instrumentCode)
  505. }
  506. }
  507. // 乐器支持多编码,暂不开放
  508. const codeIdMap = new Map<string, []>() as any
  509. state.instrumentData.forEach((data: any) => {
  510. if (!data.disabled) {
  511. const codes = data.code.split(/[,,]/)
  512. codes.forEach((code: string) => {
  513. let codeTemp = code.trim().toLowerCase()
  514. if (codeIdMap.has(codeTemp)) {
  515. codeIdMap.get(codeTemp).push(data.id + '')
  516. } else {
  517. const arr = [] as any
  518. arr.push(data.id + '')
  519. codeIdMap.set(codeTemp, arr)
  520. }
  521. })
  522. }
  523. })
  524. forms.musicalInstrumentIdList = []
  525. instrumentCodeList.forEach((code: string) => {
  526. if (codeIdMap.has(code)) {
  527. codeIdMap.get(code).forEach((c: any) => {
  528. if (!forms.musicalInstrumentIdList.includes(c)) {
  529. forms.musicalInstrumentIdList.push(c)
  530. }
  531. })
  532. }
  533. })
  534. // const codeIdMap = new Map<string, string>()
  535. // state.instrumentData.forEach((data: any) => {
  536. // if (!data.disabled) {
  537. // codeIdMap.set(data.code.toLocaleLowerCase().trim(), data.id + '')
  538. // }
  539. // })
  540. // forms.musicalInstrumentIdList = []
  541. // instrumentCodeList.forEach((code: string) => {
  542. // if (codeIdMap.has(code)) {
  543. // forms.musicalInstrumentIdList.push(codeIdMap.get(code))
  544. // }
  545. // })
  546. // 声部
  547. if (forms.musicalInstrumentIdList.length > 0) {
  548. showBackSubject(forms.musicalInstrumentIdList)
  549. }
  550. }
  551. // 获取xml中所有轨道 乐器
  552. const getPartListNames = (xml: any) => {
  553. if (!xml) return []
  554. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  555. const partList: any =
  556. xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  557. let partListNames = Array.from(partList).map((item: any) => {
  558. let part = item.getElementsByTagName('part-name')?.[0]
  559. // evxml没有分轨,需要手动设置一个默认的名称,用于上传原音
  560. // if (forms.isEvxml) {
  561. // part = part || 'noPartName'
  562. // }
  563. // 优先解析声轨,没有就取id值
  564. let track = ''
  565. if (part) {
  566. track = part.textContent || ''
  567. } else {
  568. let id = item.getAttribute('id')
  569. if (id) {
  570. track = id
  571. }
  572. }
  573. return {
  574. value: track.trim(),
  575. label: track.trim()
  576. }
  577. })
  578. // 处理空数据
  579. // if (partListNames.length === 1 && forms.details.id && !partListNames[0].value) {
  580. // partListNames[0] = {
  581. // value: forms.details.multiTracksSelection,
  582. // label: forms.details.multiTracksSelection
  583. // }
  584. // }
  585. partListNames = partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
  586. // if (partListNames.length > 0) {
  587. // forms.musicSheetSoundList = forms.musicSheetSoundList.slice(0, partListNames.length)
  588. // }
  589. state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
  590. if (!forms.playSpeed) {
  591. if (state.xmlFirstSpeed) {
  592. forms.playSpeed = Number.parseFloat(state.xmlFirstSpeed)
  593. } else {
  594. // 速度默认给100
  595. forms.playSpeed = 100
  596. }
  597. }
  598. // console.log('xml声轨',partListNames,state.xmlFirstSpeed)
  599. return partListNames
  600. }
  601. // 判断选择的音轨是否在选中
  602. const initPartsListStatus = (track: string): any => {
  603. // const _names = state.partListNames.filter(
  604. // (n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON'
  605. // )
  606. const partListNames = deepClone(state.partListNames) || []
  607. const multiTracksSelection = forms.multiTracksSelection
  608. partListNames.forEach((item: any) => {
  609. if (multiTracksSelection.includes(item.value)) {
  610. item.disabled = true
  611. } else {
  612. item.disabled = false
  613. }
  614. // const index = forms.musicSheetSoundList.findIndex(
  615. // (ground: any) => item.value == ground.track
  616. // )
  617. // if (index > -1 && track == item.value) {
  618. // item.disabled = false
  619. // } else {
  620. // item.disabled = true
  621. // }
  622. })
  623. return partListNames || []
  624. }
  625. // 反显声部
  626. const showBackSubject = async (musicalInstrumentIdList: []) => {
  627. try {
  628. const { data } = await subjectPage({
  629. page: 1,
  630. rows: 999,
  631. musicalInstrumentIdList: musicalInstrumentIdList
  632. })
  633. const tempList = data.rows || []
  634. tempList.forEach((item: any) => {
  635. forms.subjectIds.push(item.id + '')
  636. })
  637. } catch {}
  638. }
  639. // 添加原音
  640. const createSys = (initData?: any) => {
  641. forms.musicSheetSoundList.push({
  642. audioFileUrl: null, // 原音
  643. track: null, // 轨道
  644. ...initData
  645. })
  646. }
  647. // 删除原音
  648. const removeSys = (index: number) => {
  649. dialog.warning({
  650. title: '提示',
  651. content: `是否确认删除此原音?`,
  652. positiveText: '确定',
  653. negativeText: '取消',
  654. onPositiveClick: async () => {
  655. const sound = forms.musicSheetSoundList[index]
  656. let track = sound.track
  657. // if (!track) {
  658. // track = ''
  659. // }
  660. // if (track) {
  661. const selectIndex = forms.multiTracksSelection.indexOf(track)
  662. if (selectIndex > -1) {
  663. forms.multiTracksSelection.splice(selectIndex, 1)
  664. } else {
  665. forms.musicSheetSoundList.splice(index, 1)
  666. }
  667. }
  668. })
  669. }
  670. const checkMultiTracks = (value: string) => {
  671. if (!value) {
  672. return
  673. }
  674. if (value === 'all') {
  675. forms.multiTracksSelection = []
  676. state.partListNames.forEach((next: any) => {
  677. forms.multiTracksSelection.push(next.value)
  678. })
  679. } else if (value === 'invert') {
  680. state.partListNames.forEach((next: any) => {
  681. const indexOf = forms.multiTracksSelection.indexOf(next.value)
  682. if (indexOf > -1) {
  683. forms.multiTracksSelection.splice(indexOf, 1)
  684. } else {
  685. forms.multiTracksSelection.push(next.value)
  686. }
  687. })
  688. } else if (value === 'allUncheck') {
  689. forms.multiTracksSelection = []
  690. }
  691. }
  692. const setOwnerName = () => {
  693. if (forms.sourceType == 'PLATFORM') {
  694. state.ownerName = ''
  695. return
  696. }
  697. if (!forms.sourceType || !forms.musicSheetExtend?.userId) {
  698. return
  699. }
  700. const appId = forms.musicSheetExtend.applicationId
  701. const app = state.appData.filter((next: any) => {
  702. return next.id == appId
  703. }) as any
  704. if (app.length > 0) {
  705. state.ownerName = app[0].appName
  706. }
  707. if (forms.sourceType == 'ORG') {
  708. state.ownerName += '-' + forms.musicSheetExtend.organizationRole
  709. } else if (forms.sourceType == 'PERSON') {
  710. state.ownerName +=
  711. '-' +
  712. getMapValueByKey(forms.musicSheetExtend.clientType, new Map(Object.entries(clientType)))
  713. if (forms.musicSheetExtend.userName) {
  714. state.ownerName += '-' + forms.musicSheetExtend.userName
  715. }
  716. if (forms.musicSheetExtend.phone) {
  717. state.ownerName += '(' + forms.musicSheetExtend.phone + ')'
  718. }
  719. }
  720. }
  721. // 声轨数据兼容
  722. const formatTrack = (track: string) => {
  723. if (!track) {
  724. return ''
  725. }
  726. const trim = track.trim().toUpperCase()
  727. // 导入后的脏数据兼容
  728. if (trim == 'P1' || trim == 'NULL') {
  729. return ''
  730. }
  731. return track.trim()
  732. }
  733. onMounted(async () => {
  734. state.loading = true
  735. if (props.type === 'preview') {
  736. state.previewMode = true
  737. }
  738. // 获取乐器信息
  739. {
  740. if (state.instrumentList && state.instrumentList.length > 0) {
  741. return
  742. }
  743. try {
  744. const { data } = await musicalInstrumentPage({ page: 1, rows: 999 })
  745. const tempList = data.rows || []
  746. state.instrumentData = tempList
  747. tempList.forEach((item: any) => {
  748. item.label = item.name
  749. item.value = item.id + ''
  750. item.disabled = !item.enableFlag
  751. })
  752. state.instrumentList = tempList
  753. } catch {}
  754. }
  755. state.subjectList = deepClone(props.subjectList)
  756. state.subjectList.forEach((subject: any) => {
  757. subject.disabled = !subject.enableFlag
  758. })
  759. // 初始化应用
  760. {
  761. const appKeys = Object.keys(appKey)
  762. const { data } = await sysApplicationPage({ page: 1, rows: 999, parentId: 0 })
  763. const tempList = data.rows || []
  764. const filter = tempList.filter((next: any) => {
  765. return appKeys.includes(next.appKey)
  766. })
  767. filter.forEach((item: any) => {
  768. item.label = item.appName
  769. item.value = item.id
  770. })
  771. state.appData = filter
  772. }
  773. // 获取分类信息
  774. {
  775. try {
  776. const { data } = await musicSheetCategoriesQueryTree({ enable: true })
  777. state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList')
  778. } catch (e) {}
  779. }
  780. if (props.type === 'edit' || props.type === 'preview') {
  781. const detail = props.data
  782. try {
  783. const { data } = await musicSheetDetail({ id: detail.id })
  784. forms.details = data
  785. forms.playMode = data.playMode
  786. forms.xmlFileUrl = data.xmlFileUrl
  787. forms.midiUrl = data.midiUrl
  788. forms.name = data.name
  789. // forms.musicTag = data.musicTag?.split(',')
  790. forms.composer = data.composer
  791. forms.playSpeed = data.playSpeed ? Number.parseFloat(data.playSpeed) : 100
  792. // forms.showFingering = Number(data.showFingering)
  793. // forms.canEvaluate = Number(data.canEvaluate)
  794. // forms.notation = Number(data.notation)
  795. // forms.auditVersion = Number(data.auditVersion)
  796. // forms.sortNumber = data.sortNumber
  797. forms.musicCover = data.musicCover
  798. forms.remark = data.remark
  799. forms.status = data.status
  800. forms.musicSheetType = data.musicSheetType || 'SINGLE'
  801. forms.sourceType = data.sourceType
  802. forms.appAuditFlag = data.appAuditFlag ? 1 : 0
  803. forms.midiFileUrl = data.midiFileUrl
  804. forms.isShowFingering = data.isShowFingering
  805. forms.subjectIds = []
  806. if (data.subjectIds) {
  807. const subjectIds = data.subjectIds.split(',') || []
  808. subjectIds.forEach((subjectId: any) => {
  809. if (!forms.subjectIds.includes(subjectId)) {
  810. forms.subjectIds.push(subjectId)
  811. }
  812. })
  813. state.subjectList = state.subjectList.filter((subject: any) => {
  814. return !subject.disabled || subjectIds.includes(subject.value)
  815. })
  816. }
  817. forms.musicalInstrumentIdList = data.musicalInstrumentIds
  818. ? data.musicalInstrumentIds.split(',')
  819. : []
  820. forms.musicCategoryId = data.musicCategoryId
  821. data.musicSheetAccompanimentList?.forEach((next: any) => {
  822. state.musicSheetAccompanimentUrlList.push(next.audioFileUrl)
  823. })
  824. forms.musicSheetAccompanimentList = data.musicSheetAccompanimentList
  825. forms.audioType = data.audioType
  826. forms.isPlayBeat = data.isPlayBeat
  827. forms.isUseSystemBeat = data.isUseSystemBeat
  828. // 获取渐变 和 是否多声部
  829. try {
  830. const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
  831. forms.graduals = extConfigJson.gradualTimes || {}
  832. forms.repeatedBeats = !!extConfigJson.repeatedBeats
  833. forms.isEvxml = !!extConfigJson.isEvxml
  834. } catch (error) {}
  835. forms.evaluationStandard = data.evaluationStandard
  836. forms.musicSheetExtend = data.musicSheetExtend
  837. setOwnerName()
  838. axios.get(data.xmlFileUrl).then((res: any) => {
  839. if (res?.data) {
  840. gradualData.list = getGradualLengthByXml(res?.data as any).filter(
  841. (item: any) => item.length === 2
  842. )
  843. state.partListNames = getPartListNames(res?.data as any) as any
  844. // 初始化音轨和原音
  845. let tracks = state.partListNames.map((next: any) => next.label)
  846. if (data.multiTracksSelection) {
  847. forms.multiTracksSelection = data.multiTracksSelection
  848. .toLocaleUpperCase()
  849. .split(',')
  850. } else {
  851. forms.multiTracksSelection.push('')
  852. }
  853. forms.multiTracksSelection = tracks.filter((next: any) =>
  854. forms.multiTracksSelection.includes(next.toLocaleUpperCase())
  855. )
  856. const existSoundList = data.musicSheetSoundList ? data.musicSheetSoundList : []
  857. // 如果只有一个原音文件,并且原音没有对应声轨,取xml解析中的第一个声轨绑定当当前原音
  858. if (existSoundList.length === 1 && !formatTrack(existSoundList[0].track)) {
  859. let track = state.partListNames.length > 0 ? state.partListNames[0].value : null
  860. forms.musicSheetSoundList.push({
  861. audioFileUrl: existSoundList[0].audioFileUrl, // 原音
  862. musicalInstrumentId: existSoundList[0].musicalInstrumentId,
  863. track: track // 轨道
  864. })
  865. if (track && !forms.multiTracksSelection.includes(track)) {
  866. forms.multiTracksSelection.push(track)
  867. }
  868. } else {
  869. state.partListNames.forEach((item: any) => {
  870. let audioFileUrl = null
  871. let musicalInstrumentId = null
  872. existSoundList.forEach((next: any) => {
  873. if (!next.track || next.track.trim() == '') {
  874. next.track = ''
  875. }
  876. if (next.track == item.value) {
  877. audioFileUrl = next.audioFileUrl
  878. musicalInstrumentId = next.musicalInstrumentId
  879. }
  880. })
  881. forms.musicSheetSoundList.push({
  882. audioFileUrl: audioFileUrl, // 原音
  883. musicalInstrumentId: musicalInstrumentId, // 乐器
  884. track: item.value // 轨道
  885. })
  886. })
  887. if (tracks.length == forms.multiTracksSelection.length) {
  888. state.multiTracks = 'all'
  889. }
  890. // 处理没有声轨,但有原音
  891. existSoundList
  892. .filter((next: any) => {
  893. return !tracks.includes(next.track)
  894. })
  895. .forEach((next: any) => {
  896. forms.musicSheetSoundList.push({
  897. audioFileUrl: next.audioFileUrl, // 原音
  898. musicalInstrumentId: next.musicalInstrumentId,
  899. track: next.track ? next.track : null // 轨道
  900. })
  901. })
  902. }
  903. }
  904. })
  905. } catch (error) {}
  906. } else {
  907. // 新增只能使用启用状态的数据
  908. state.subjectList = state.subjectList.filter((next: any) => {
  909. return next.enableFlag == true
  910. })
  911. state.instrumentList = state.instrumentList.filter((next: any) => {
  912. return next.enableFlag == true
  913. })
  914. }
  915. state.loading = false
  916. })
  917. return () => (
  918. <div style="background: #fff; padding-top: 12px">
  919. <NSpin show={state.loading}>
  920. <NForm
  921. class={styles.formContainer}
  922. model={forms}
  923. ref={formsRef}
  924. label-placement="left"
  925. label-width="150"
  926. disabled={state.previewMode}
  927. >
  928. <NAlert showIcon={false} style={{ marginBottom: '12px' }}>
  929. 曲目信息
  930. </NAlert>
  931. <NGrid cols={2}>
  932. <NFormItemGi
  933. label="曲目名称"
  934. path="name"
  935. rule={[
  936. {
  937. required: true,
  938. message: '请输入曲目名称',
  939. trigger: ['input', 'blur']
  940. }
  941. ]}
  942. >
  943. <NInput
  944. v-model:value={forms.name}
  945. placeholder="请输入曲目名称"
  946. maxlength={50}
  947. showCount
  948. />
  949. </NFormItemGi>
  950. <NFormItemGi
  951. label="音乐人"
  952. path="composer"
  953. rule={[
  954. {
  955. required: true,
  956. message: '请输入音乐人',
  957. trigger: ['input', 'blur']
  958. }
  959. ]}
  960. >
  961. <NInput
  962. v-model:value={forms.composer}
  963. placeholder="请输入音乐人名称"
  964. showCount
  965. maxlength={14}
  966. />
  967. </NFormItemGi>
  968. </NGrid>
  969. <NGrid cols={2}>
  970. <NFormItemGi label="曲目描述" path="remark">
  971. <NInput
  972. placeholder="请输入曲目描述"
  973. type="textarea"
  974. rows={4}
  975. showCount
  976. maxlength={200}
  977. v-model:value={forms.remark}
  978. />
  979. </NFormItemGi>
  980. <NFormItemGi
  981. label="曲目封面"
  982. path="musicCover"
  983. rule={[
  984. {
  985. required: false,
  986. message: '请上传曲目封面',
  987. trigger: ['input', 'blur']
  988. }
  989. ]}
  990. >
  991. <UploadFile
  992. desc={'封面图'}
  993. disabled={state.previewMode}
  994. accept=".jpg,.jpeg,.png"
  995. tips="请上传大小1M以内的JPG、PNG图片"
  996. size={1}
  997. v-model:fileList={forms.musicCover}
  998. cropper
  999. bucketName="cbs"
  1000. options={{
  1001. autoCrop: true, //是否默认生成截图框
  1002. enlarge: 2, // 图片放大倍数
  1003. autoCropWidth: 200, //默框高度
  1004. fixedBox: true, //是否固定截图框大认生成截图框宽度
  1005. autoCropHeight: 200, //默认生成截图小 不允许改变
  1006. previewsCircle: false, //预览图是否是原圆形
  1007. title: '曲目封面'
  1008. }}
  1009. />
  1010. </NFormItemGi>
  1011. </NGrid>
  1012. <NGrid cols={2}>
  1013. <NFormItemGi
  1014. label="作者属性"
  1015. path="sourceType"
  1016. rule={[
  1017. {
  1018. required: true,
  1019. message: '请选择作者属性',
  1020. trigger: 'change'
  1021. }
  1022. ]}
  1023. >
  1024. <NSelect
  1025. v-model:value={forms.sourceType}
  1026. options={getSelectDataFromObj(musicSheetSourceType)}
  1027. placeholder="请选择作者属性"
  1028. onUpdateValue={() => {
  1029. // 发送变化,清理选择的所属人信息
  1030. forms.musicSheetExtend = {}
  1031. state.ownerName = null
  1032. // forms.musicSheetExtend.userId = null
  1033. // forms.musicSheetExtend.userName = null
  1034. // forms.musicSheetExtend.applicationId = null
  1035. // forms.musicSheetExtend.organizationRoleId = null
  1036. }}
  1037. />
  1038. </NFormItemGi>
  1039. {forms.sourceType === 'PERSON' && (
  1040. <NFormItemGi
  1041. label="所属人"
  1042. path="musicSheetExtend.userId"
  1043. rule={[
  1044. {
  1045. required: true,
  1046. message: '请选择曲目所属人',
  1047. trigger: ['input', 'change']
  1048. }
  1049. ]}
  1050. >
  1051. <NButton
  1052. disabled={state.previewMode || !forms.sourceType}
  1053. type="primary"
  1054. size="small"
  1055. text
  1056. //v-auth="orchestraSubsidyStandard/update1597887579789053953"
  1057. onClick={() => {
  1058. state.showMusicSheetOwnerDialog = true
  1059. }}
  1060. >
  1061. {state.ownerName ? state.ownerName : '请选择所属人'}
  1062. </NButton>
  1063. </NFormItemGi>
  1064. )}
  1065. {forms.sourceType === 'ORG' && (
  1066. <NFormItemGi
  1067. label="所属人"
  1068. path="musicSheetExtend.organizationRoleId"
  1069. rule={[
  1070. {
  1071. required: true,
  1072. message: '请选择曲目所属机构',
  1073. trigger: ['input', 'change']
  1074. }
  1075. ]}
  1076. >
  1077. <NButton
  1078. disabled={state.previewMode || !forms.sourceType}
  1079. type="primary"
  1080. size="small"
  1081. text
  1082. //v-auth="orchestraSubsidyStandard/update1597887579789053953"
  1083. onClick={() => {
  1084. state.showMusicSheetOwnerDialog = true
  1085. }}
  1086. >
  1087. {state.ownerName ? state.ownerName : '请选择所属机构'}
  1088. </NButton>
  1089. </NFormItemGi>
  1090. )}
  1091. </NGrid>
  1092. <NGrid cols={2}>
  1093. <NFormItemGi
  1094. label="审核版本"
  1095. path="appAuditFlag"
  1096. rule={[
  1097. {
  1098. required: true,
  1099. message: '请选择审核版本',
  1100. trigger: 'change',
  1101. type: 'number'
  1102. }
  1103. ]}
  1104. >
  1105. <NSelect
  1106. options={
  1107. [
  1108. {
  1109. label: '是',
  1110. value: 1
  1111. },
  1112. {
  1113. label: '否',
  1114. value: 0
  1115. }
  1116. ] as any
  1117. }
  1118. v-model:value={forms.appAuditFlag}
  1119. />
  1120. </NFormItemGi>
  1121. <NFormItemGi
  1122. label="曲目分类"
  1123. path="musicCategoryId"
  1124. rule={[
  1125. {
  1126. required: true,
  1127. message: '请选择曲目分类',
  1128. trigger: ['change']
  1129. }
  1130. ]}
  1131. >
  1132. <NCascader
  1133. valueField="id"
  1134. labelField="name"
  1135. children-field="musicSheetCategoriesList"
  1136. placeholder="请选择分类"
  1137. v-model:value={forms.musicCategoryId}
  1138. options={state.musicSheetCategories}
  1139. clearable
  1140. />
  1141. </NFormItemGi>
  1142. </NGrid>
  1143. <NGrid cols={2}>
  1144. <NFormItemGi
  1145. label="重复节拍时长"
  1146. path="repeatedBeats"
  1147. rule={[
  1148. {
  1149. required: false,
  1150. message: '请选择是否重复节拍时长'
  1151. }
  1152. ]}
  1153. >
  1154. <NRadioGroup v-model:value={forms.repeatedBeats}>
  1155. <NRadio value={true}>是</NRadio>
  1156. <NRadio value={false}>否</NRadio>
  1157. </NRadioGroup>
  1158. </NFormItemGi>
  1159. <NFormItemGi
  1160. label="评分标准"
  1161. path="evaluationStandard"
  1162. rule={[
  1163. {
  1164. required: true
  1165. }
  1166. ]}
  1167. >
  1168. <NRadioGroup v-model:value={forms.evaluationStandard}>
  1169. <NRadio value={'FREQUENCY'}>标准评测</NRadio>
  1170. <NRadio value={'AMPLITUDE'}>打击乐(振幅)</NRadio>
  1171. <NRadio value={'DECIBELS'}>节奏(分贝)</NRadio>
  1172. </NRadioGroup>
  1173. </NFormItemGi>
  1174. </NGrid>
  1175. <NGrid cols={2}>
  1176. <NFormItemGi
  1177. label="速度"
  1178. path="playSpeed"
  1179. rule={[
  1180. {
  1181. required: true,
  1182. message: '请输入速度'
  1183. }
  1184. ]}
  1185. >
  1186. <NInputNumber
  1187. placeholder="请输入速度"
  1188. v-model:value={forms.playSpeed}
  1189. min="0"
  1190. style="width:100%"
  1191. />
  1192. </NFormItemGi>
  1193. </NGrid>
  1194. <NAlert showIcon={false} style={{ marginBottom: '12px' }}>
  1195. 曲目上传
  1196. </NAlert>
  1197. <NGrid cols={2}>
  1198. <NFormItemGi
  1199. label="播放模式"
  1200. path="playMode"
  1201. rule={[
  1202. {
  1203. required: true,
  1204. message: '请选择播放模式'
  1205. }
  1206. ]}
  1207. >
  1208. <NRadioGroup
  1209. v-model:value={forms.playMode}
  1210. onUpdateValue={(value: string | number | boolean) => {
  1211. if (value === 'MP3') {
  1212. forms.playMode = 'MP3'
  1213. } else {
  1214. forms.playMode = 'MIDI'
  1215. }
  1216. }}
  1217. >
  1218. <NRadio value="MP3">MP3</NRadio>
  1219. <NRadio value="MIDI">MID</NRadio>
  1220. </NRadioGroup>
  1221. </NFormItemGi>
  1222. {forms.playMode === 'MP3' && (
  1223. <NFormItemGi
  1224. label="伴奏类型"
  1225. path="audioType"
  1226. rule={[
  1227. {
  1228. required: true,
  1229. message: '请选择伴奏类型'
  1230. }
  1231. ]}
  1232. >
  1233. <NRadioGroup v-model:value={forms.audioType}>
  1234. <NRadio value={'HOMEMODE'}>自制伴奏</NRadio>
  1235. <NRadio value={'COMMON'}>普通伴奏</NRadio>
  1236. </NRadioGroup>
  1237. </NFormItemGi>
  1238. )}
  1239. </NGrid>
  1240. <NGrid cols={2}>
  1241. {forms.playMode === 'MP3' && (
  1242. <NFormItemGi
  1243. label="上传伴奏"
  1244. path="musicSheetAccompanimentList"
  1245. rule={[
  1246. {
  1247. required: false,
  1248. message: '请选择上传.mp3'
  1249. }
  1250. ]}
  1251. >
  1252. <UploadFile
  1253. disabled={state.previewMode}
  1254. size={30}
  1255. v-model:imageList={state.musicSheetAccompanimentUrlList}
  1256. tips="仅支持上传.mp3格式文件"
  1257. listType="image"
  1258. accept=".mp3"
  1259. bucketName="cloud-coach"
  1260. text="点击上传伴奏文件"
  1261. max={10}
  1262. desc={'上传伴奏文件'}
  1263. onUpload:success={(file) => {
  1264. state.musicSheetAccompanimentUrls = [
  1265. state.musicSheetAccompanimentUrls,
  1266. file.url
  1267. ]
  1268. .filter(Boolean)
  1269. .join(',')
  1270. state.musicSheetAccompanimentUrlList = state.musicSheetAccompanimentUrls
  1271. ?.split(',')
  1272. .filter(Boolean)
  1273. forms.musicSheetAccompanimentList = []
  1274. for (let i = 0; i < state.musicSheetAccompanimentUrlList.length; i++) {
  1275. forms.musicSheetAccompanimentList.push({
  1276. audioFileUrl: state.musicSheetAccompanimentUrlList[i],
  1277. sortNumber: i + 1
  1278. })
  1279. }
  1280. }}
  1281. onRemove={() => {
  1282. state.musicSheetAccompanimentUrlList = []
  1283. state.musicSheetAccompanimentUrls = ''
  1284. }}
  1285. // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  1286. multiple={true}
  1287. />
  1288. </NFormItemGi>
  1289. )}
  1290. {forms.playMode === 'MIDI' && (
  1291. <NFormItemGi
  1292. label="上传MID"
  1293. path="midiFileUrl"
  1294. rule={[
  1295. {
  1296. required: true,
  1297. message: '请选择上传.MID格式文件'
  1298. }
  1299. ]}
  1300. >
  1301. <UploadFile
  1302. desc={'MIDI文件'}
  1303. disabled={state.previewMode}
  1304. size={30}
  1305. v-model:fileList={forms.midiFileUrl}
  1306. tips="仅支持上传.MID格式文件"
  1307. listType="image"
  1308. accept=".mid"
  1309. bucketName="cloud-coach"
  1310. text="点击上传MID文件"
  1311. // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  1312. />
  1313. </NFormItemGi>
  1314. )}
  1315. <NFormItemGi
  1316. label="上传XML"
  1317. path="xmlFileUrl"
  1318. rule={[
  1319. {
  1320. required: true,
  1321. message: '请选择上传XML',
  1322. trigger: ['change', 'input']
  1323. }
  1324. ]}
  1325. >
  1326. <UploadFile
  1327. desc={'XML文件'}
  1328. disabled={state.previewMode}
  1329. size={30}
  1330. key={'xmlFileUrl'}
  1331. v-model:fileList={forms.xmlFileUrl}
  1332. tips="仅支持上传.xml/.mxml格式文件"
  1333. listType="image"
  1334. accept=".xml,.mxml,.evxml"
  1335. bucketName="cloud-coach"
  1336. text="点击上传XML文件"
  1337. onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  1338. onRemove={() => {
  1339. // forms.multiTracksSelection = []
  1340. // state.partListNames = []
  1341. // forms.musicSheetSoundList = []
  1342. forms.musicalInstrumentIdList = []
  1343. forms.subjectIds = []
  1344. }}
  1345. />
  1346. </NFormItemGi>
  1347. </NGrid>
  1348. <NGrid cols={2}>
  1349. <NFormItemGi
  1350. label="可用声部"
  1351. path="subjectIds"
  1352. rule={[
  1353. {
  1354. required: true,
  1355. message: '请选择可用声部',
  1356. trigger: 'change',
  1357. type: 'array'
  1358. }
  1359. ]}
  1360. >
  1361. <NSelect
  1362. v-model:value={forms.subjectIds}
  1363. options={state.subjectList}
  1364. multiple
  1365. filterable
  1366. clearable
  1367. placeholder="请选择可用声部"
  1368. maxTagCount={2}
  1369. />
  1370. </NFormItemGi>
  1371. <NFormItemGi
  1372. label="可用乐器"
  1373. path="musicalInstrumentIdList"
  1374. rule={[
  1375. {
  1376. required: true,
  1377. message: '请选择可用乐器',
  1378. trigger: 'change',
  1379. type: 'array'
  1380. }
  1381. ]}
  1382. >
  1383. <NSelect
  1384. placeholder="请选择可用乐器"
  1385. options={state.instrumentList}
  1386. v-model:value={forms.musicalInstrumentIdList}
  1387. clearable
  1388. multiple
  1389. filterable
  1390. maxTagCount={2}
  1391. />
  1392. </NFormItemGi>
  1393. </NGrid>
  1394. <NGrid cols={2}>
  1395. <NFormItemGi
  1396. label="多声轨渲染"
  1397. path="musicSheetType"
  1398. rule={[
  1399. {
  1400. required: true,
  1401. message: '请选择多声轨渲染',
  1402. trigger: 'change'
  1403. }
  1404. ]}
  1405. >
  1406. {/*<NSelect*/}
  1407. {/* placeholder="请选择曲目类型"*/}
  1408. {/* v-model:value={forms.musicSheetType}*/}
  1409. {/* options={getSelectDataFromObj(musicSheetType)}*/}
  1410. {/*/>*/}
  1411. <NRadioGroup v-model:value={forms.musicSheetType}>
  1412. <NRadio value={'SINGLE'}>是</NRadio>
  1413. <NRadio value={'CONCERT'}>否</NRadio>
  1414. </NRadioGroup>
  1415. </NFormItemGi>
  1416. <NFormItemGi
  1417. label="是否显示指法"
  1418. path="isShowFingering"
  1419. rule={[
  1420. {
  1421. required: true,
  1422. message: '请选择是否显示指法'
  1423. }
  1424. ]}
  1425. >
  1426. <NRadioGroup v-model:value={forms.isShowFingering}>
  1427. <NRadio value={true}>是</NRadio>
  1428. <NRadio value={false}>否</NRadio>
  1429. </NRadioGroup>
  1430. </NFormItemGi>
  1431. </NGrid>
  1432. {forms.musicSheetType && (
  1433. <NGrid cols={1}>
  1434. <NFormItemGi
  1435. label={`${forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'}`}
  1436. path="multiTracksSelection"
  1437. rule={[
  1438. {
  1439. required: true,
  1440. message: `请选择${
  1441. forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'
  1442. }`,
  1443. trigger: 'change',
  1444. type: 'array'
  1445. }
  1446. ]}
  1447. >
  1448. <NGrid style="padding-top: 4px;">
  1449. <NGi span={24}>
  1450. <NRadioGroup
  1451. v-model:value={state.multiTracks}
  1452. onUpdateValue={(value) => {
  1453. checkMultiTracks(value)
  1454. }}
  1455. >
  1456. <NRadio value={'all'}>全选</NRadio>
  1457. <NRadio value={'allUncheck'}>重置</NRadio>
  1458. <NRadio value={'invert'}>反选</NRadio>
  1459. </NRadioGroup>
  1460. </NGi>
  1461. {state.partListNames && state.partListNames.length > 0 && (
  1462. <NGi span={24} style={'margin-top:5px'}>
  1463. <NFormItemGi
  1464. label=""
  1465. path="multiTracksSelection"
  1466. rule={[
  1467. {
  1468. required: false
  1469. }
  1470. ]}
  1471. >
  1472. <NCheckboxGroup
  1473. v-model:value={forms.multiTracksSelection}
  1474. onUpdateValue={(val: any) => {
  1475. if (state.partListNames.length != val.length) {
  1476. state.multiTracks = null
  1477. } else {
  1478. state.multiTracks = 'all'
  1479. }
  1480. //
  1481. // let removeTracks = [] as any
  1482. // for (let i = 0; i < forms.multiTracksSelection.length; i++) {
  1483. // let track = forms.musicSheetSoundList[i].track
  1484. // if (!val.includes(track)) {
  1485. // removeTracks.push(track)
  1486. // }
  1487. // }
  1488. // // 新增
  1489. // for (let i = 0; i < val.length; i++) {
  1490. // let track = val.value;
  1491. // let contain = false
  1492. // for (let j = 0; j < forms.musicSheetSoundList.length; j++) {
  1493. // let track1 = forms.musicSheetSoundList[i].track;
  1494. // if (track == track1) {
  1495. // contain = true
  1496. // break
  1497. // }
  1498. // }
  1499. // if (!contain) {
  1500. // forms.musicSheetSoundList.push({audioFileUrl: null, track: track, musicalInstrumentId: null})
  1501. // }
  1502. // }
  1503. // // 删除
  1504. // if (removeTracks.length > 0) {
  1505. // forms.musicSheetSoundList = forms.musicSheetSoundList.filter((val: any) => {
  1506. // return removeTracks.includes(val)
  1507. // })
  1508. // }
  1509. // forms.multiTracksSelection = val
  1510. }}
  1511. >
  1512. <NGrid yGap={2} cols={4}>
  1513. {state.partListNames.map((item: any) => (
  1514. <NGi>
  1515. <NCheckbox value={item.value} label={item.label} />
  1516. </NGi>
  1517. ))}
  1518. </NGrid>
  1519. </NCheckboxGroup>
  1520. </NFormItemGi>
  1521. </NGi>
  1522. )}
  1523. </NGrid>
  1524. </NFormItemGi>
  1525. </NGrid>
  1526. )}
  1527. <NGrid cols={2}>
  1528. <NFormItemGi
  1529. label="是否播放节拍器"
  1530. path="isPlayBeat"
  1531. rule={[
  1532. {
  1533. required: true,
  1534. message: '请选择是否播放节拍器'
  1535. }
  1536. ]}
  1537. >
  1538. <NRadioGroup v-model:value={forms.isPlayBeat}>
  1539. <NRadio value={true}>是</NRadio>
  1540. <NRadio value={false}>否</NRadio>
  1541. </NRadioGroup>
  1542. </NFormItemGi>
  1543. {forms.isPlayBeat && (
  1544. <NFormItemGi
  1545. label="播放方式"
  1546. path="isUseSystemBeat"
  1547. rule={[
  1548. {
  1549. required: true,
  1550. message: '请选择播放方式'
  1551. }
  1552. ]}
  1553. >
  1554. <NRadioGroup v-model:value={forms.isUseSystemBeat}>
  1555. <NRadio value={true}>系统节拍器</NRadio>
  1556. <NRadio value={false}>MP3节拍器</NRadio>
  1557. </NRadioGroup>
  1558. </NFormItemGi>
  1559. )}
  1560. </NGrid>
  1561. {!!gradualData.list.length && (
  1562. <>
  1563. <NAlert showIcon={false} type="info">
  1564. 识别到共1处渐变速度,请输入Dorico对应小节时间信息
  1565. </NAlert>
  1566. <NFormItem label="rit." required style={{ marginTop: '10px' }}>
  1567. <NSpace vertical>
  1568. {gradualData.list.map((n: any, ni: number) => (
  1569. <NInputGroup>
  1570. <NFormItem
  1571. path={`graduals.${n[0].measureIndex}`}
  1572. rule={[
  1573. { required: true, message: '请输入合奏曲目时间' },
  1574. {
  1575. pattern: /^((\d{2}):?){2,3}$/,
  1576. message: '请输入正确的曲目时间',
  1577. trigger: 'blur'
  1578. }
  1579. ]}
  1580. >
  1581. <NInputGroup>
  1582. <NInputGroupLabel>{n[0].measureIndex + 1}小节开始</NInputGroupLabel>
  1583. <NInput
  1584. placeholder="00:00:00"
  1585. v-model:value={forms.graduals[n[0].measureIndex]}
  1586. ></NInput>
  1587. </NInputGroup>
  1588. </NFormItem>
  1589. <div style={{ lineHeight: '30px', padding: '0 4px' }}>~</div>
  1590. <NFormItem
  1591. path={`graduals.${n[1].measureIndex}`}
  1592. rule={[
  1593. { required: true, message: '请输入合奏曲目时间' },
  1594. {
  1595. pattern: /^((\d{2}):?){2,3}$/,
  1596. message: '请输入正确的曲目时间',
  1597. trigger: 'blur'
  1598. }
  1599. ]}
  1600. >
  1601. <NInputGroup>
  1602. <NInput
  1603. placeholder="00:00:00"
  1604. v-model:value={forms.graduals[n[1].measureIndex]}
  1605. ></NInput>
  1606. <NInputGroupLabel>{n[1].measureIndex}小节结束</NInputGroupLabel>
  1607. </NInputGroup>
  1608. </NFormItem>
  1609. </NInputGroup>
  1610. ))}
  1611. </NSpace>
  1612. </NFormItem>
  1613. </>
  1614. )}
  1615. {/* 只有播放类型为mp3时才会有原音 */}
  1616. {forms.playMode === 'MP3' && forms.musicSheetSoundList.length > 0 && (
  1617. <>
  1618. {forms.musicSheetSoundList.map((item: any, index: number) => {
  1619. return (
  1620. <>
  1621. {(!containOther(item.track) ||
  1622. (item.track?.toLocaleUpperCase?.() != 'COMMON' &&
  1623. forms.multiTracksSelection.includes(item.track))) && (
  1624. <NGrid
  1625. class={styles.audioSection}
  1626. // v-show={forms.multiTracksSelection.indexOf(item.track) > -1}
  1627. >
  1628. <NFormItemGi
  1629. span={12}
  1630. label="原音"
  1631. path={`musicSheetSoundList[${index}].audioFileUrl`}
  1632. rule={[
  1633. {
  1634. // required: forms.multiTracksSelection.indexOf(forms.musicSheetSoundList[index].audioFileUrl) > -1,
  1635. required: true,
  1636. message: `请上传${
  1637. item.track ? item.track + '的' : '第' + (index + 1) + '个'
  1638. }原音`
  1639. }
  1640. ]}
  1641. >
  1642. <UploadFile
  1643. desc={'原音文件'}
  1644. disabled={state.previewMode}
  1645. size={100}
  1646. v-model:fileList={item.audioFileUrl}
  1647. tips="仅支持上传.mp3格式文件"
  1648. listType="image"
  1649. accept=".mp3"
  1650. bucketName="cloud-coach"
  1651. />
  1652. </NFormItemGi>
  1653. {state.partListNames.length > 0 && (
  1654. <NFormItemGi
  1655. span={12}
  1656. label="所属轨道"
  1657. path={`musicSheetSoundList[${index}].track`}
  1658. rule={[
  1659. {
  1660. required: validSoundNum() > 1 && item.track == null,
  1661. message: '请选择所属轨道'
  1662. }
  1663. ]}
  1664. >
  1665. <NSelect
  1666. placeholder="请选择所属轨道"
  1667. value={item.track}
  1668. options={initPartsListStatus(item.track)}
  1669. onUpdateValue={(value: any) => {
  1670. const track = item.track
  1671. if (track) {
  1672. // 声轨交换
  1673. forms.musicSheetSoundList.forEach((next: any) => {
  1674. if (next.track == value) {
  1675. next.track = track
  1676. }
  1677. })
  1678. const index = forms.multiTracksSelection.indexOf(item.track)
  1679. forms.multiTracksSelection.splice(index, 1)
  1680. } else {
  1681. forms.musicSheetSoundList = forms.musicSheetSoundList.filter(
  1682. (next: any) => {
  1683. return next.track != value
  1684. }
  1685. )
  1686. }
  1687. if (
  1688. value != null &&
  1689. !forms.multiTracksSelection.includes(value)
  1690. ) {
  1691. forms.multiTracksSelection.push(value)
  1692. }
  1693. item.track = value
  1694. }}
  1695. />
  1696. </NFormItemGi>
  1697. )}
  1698. <NGi class={styles.btnRemove}>
  1699. <NButton
  1700. type="primary"
  1701. text
  1702. // disabled={forms.musicSheetSoundList.length === 1}
  1703. onClick={() => removeSys(index)}
  1704. >
  1705. 删除
  1706. </NButton>
  1707. </NGi>
  1708. </NGrid>
  1709. )}
  1710. </>
  1711. )
  1712. })}
  1713. </>
  1714. )}
  1715. </NForm>
  1716. </NSpin>
  1717. {props.type !== 'preview' && (
  1718. <NSpace justify="end" style="padding-top:12px">
  1719. <NButton type="default" onClick={() => emit('close')}>
  1720. 取消
  1721. </NButton>
  1722. <NButton
  1723. type="primary"
  1724. onClick={() => onSubmit()}
  1725. loading={btnLoading.value}
  1726. disabled={btnLoading.value}
  1727. >
  1728. 确认
  1729. </NButton>
  1730. </NSpace>
  1731. )}
  1732. <NModal
  1733. v-model:show={state.showMusicSheetOwnerDialog}
  1734. preset="dialog"
  1735. showIcon={false}
  1736. maskClosable={false}
  1737. title="所属人"
  1738. style={{ width: '800px' }}
  1739. >
  1740. <MusicSheetOwnerDialog
  1741. musicSheetExtend={forms.musicSheetExtend}
  1742. sourceType={forms.sourceType}
  1743. appData={state.appData}
  1744. onClose={() => {
  1745. state.showMusicSheetOwnerDialog = false
  1746. }}
  1747. onChoseMusicSheetOwnerData={(musicSheetOwnerData) => {
  1748. forms.musicSheetExtend = {
  1749. ...musicSheetOwnerData
  1750. }
  1751. setOwnerName()
  1752. }}
  1753. />
  1754. </NModal>
  1755. <NModal
  1756. class={styles.productModal}
  1757. title="自动生成曲谱图片"
  1758. v-model:show={state.productOpen}
  1759. preset="dialog"
  1760. closeOnEsc={false}
  1761. maskClosable={false}
  1762. showIcon={false}
  1763. >
  1764. <MusicCreateImg
  1765. xmlFileUrl={forms.xmlFileUrl || ''}
  1766. onClose={() => (state.productOpen = false)}
  1767. onConfirm={async (item: any) => {
  1768. // 保存
  1769. try {
  1770. forms.musicImg = item.musicImg
  1771. forms.musicFirstImg = item.musicFirstImg
  1772. forms.musicJianImg = item.musicJianImg
  1773. onSubmit()
  1774. } catch (e: any) {
  1775. //
  1776. console.log(e, 'e')
  1777. }
  1778. setTimeout(() => {
  1779. state.isAutoSave = false
  1780. }, 50)
  1781. }}
  1782. />
  1783. </NModal>
  1784. </div>
  1785. )
  1786. }
  1787. })