music-operation.tsx 67 KB

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