music-operation.tsx 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249
  1. import type {SelectOption} from 'naive-ui'
  2. import {
  3. NAlert,
  4. NButton,
  5. NCascader,
  6. NCheckbox,
  7. NForm,
  8. NFormItemGi,
  9. NGi,
  10. NGrid,
  11. NInput,
  12. NInputNumber,
  13. NModal,
  14. NRadio,
  15. NRadioGroup,
  16. NSelect,
  17. NSpace,
  18. useDialog,
  19. useMessage,
  20. NCheckboxGroup, NCol
  21. } from 'naive-ui'
  22. import {defineComponent, onMounted, PropType, reactive, ref} from 'vue'
  23. import {musicSheetDetail, musicSheetSave, musicSheetUpdate} from '../../api'
  24. import UploadFile from '@/components/upload-file'
  25. import styles from './index.module.less'
  26. import deepClone from '@/utils/deep.clone'
  27. import axios from 'axios'
  28. import {musicSheetSourceType, musicSheetType} from "@/utils/constant";
  29. import {getSelectDataFromObj} from "@/utils/objectUtil";
  30. import {musicalInstrumentPage} from "@views/system-manage/subject-manage/api";
  31. import {subjectPage} from "@views/system-manage/api";
  32. import MusicSheetOwnerDialog from "@views/music-library/music-sheet/modal/musicSheetOwnerDialog";
  33. /**
  34. * 获取指定元素下一个Note元素
  35. * @param ele 指定元素
  36. * @param selectors 选择器
  37. */
  38. const getNextNote = (ele: any, selectors: any) => {
  39. let index = 0
  40. const parentEle = ele.closest(selectors)
  41. let pointer = parentEle
  42. const measure = parentEle?.closest('measure')
  43. let siblingNote = null
  44. // 查找到相邻的第一个note元素
  45. while (!siblingNote && index < (measure?.childNodes.length || 50)) {
  46. index++
  47. if (pointer?.nextElementSibling?.tagName === 'note') {
  48. siblingNote = pointer?.nextElementSibling
  49. }
  50. pointer = pointer?.nextElementSibling
  51. }
  52. return siblingNote
  53. }
  54. export const onlyVisible = (xml: any, partIndex: any) => {
  55. if (!xml) return ''
  56. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  57. const partList =
  58. xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  59. const parts = xmlParse.getElementsByTagName('part')
  60. const visiblePartInfo = partList[partIndex]
  61. if (visiblePartInfo) {
  62. const id = visiblePartInfo.getAttribute('id')
  63. Array.from(parts).forEach((part) => {
  64. if (part && part.getAttribute('id') !== id) {
  65. part.parentNode?.removeChild(part)
  66. // 不等于第一行才添加避免重复添加
  67. }
  68. // 最后一个小节的结束线元素不在最后 调整
  69. if (part && part.getAttribute('id') === id) {
  70. const barlines = part.getElementsByTagName('barline')
  71. const lastParent = barlines[barlines.length - 1]?.parentElement
  72. if (lastParent?.lastElementChild?.tagName !== 'barline') {
  73. const children: any[] = (lastParent?.children as any) || []
  74. for (let el of children) {
  75. if (el.tagName === 'barline') {
  76. // 将结束线元素放到最后
  77. lastParent?.appendChild(el)
  78. break
  79. }
  80. }
  81. }
  82. }
  83. })
  84. Array.from(partList).forEach((part) => {
  85. if (part && part.getAttribute('id') !== id) {
  86. part.parentNode?.removeChild(part)
  87. }
  88. })
  89. // 处理装饰音问题
  90. const notes = xmlParse.getElementsByTagName('note')
  91. const getNextvNoteDuration = (i: any) => {
  92. let nextNote = notes[i + 1]
  93. // 可能存在多个装饰音问题,取下一个非装饰音时值
  94. for (let index = i; index < notes.length; index++) {
  95. const note = notes[index]
  96. if (!note.getElementsByTagName('grace')?.length) {
  97. nextNote = note
  98. break
  99. }
  100. }
  101. return nextNote?.getElementsByTagName('duration')[0]
  102. }
  103. Array.from(notes).forEach((note, i) => {
  104. const graces = note.getElementsByTagName('grace')
  105. if (graces && graces.length) {
  106. note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
  107. }
  108. })
  109. }
  110. return new XMLSerializer().serializeToString(xmlParse)
  111. }
  112. const speedInfo = {
  113. 'rall.': 1.333333333,
  114. 'poco rit.': 1.333333333,
  115. 'rit.': 1.333333333,
  116. 'molto rit.': 1.333333333,
  117. 'molto rall': 1.333333333,
  118. molto: 1.333333333,
  119. lentando: 1.333333333,
  120. allargando: 1.333333333,
  121. morendo: 1.333333333,
  122. 'accel.': 0.8,
  123. calando: 2,
  124. 'poco accel.': 0.8,
  125. 'gradually slowing': 1.333333333,
  126. slowing: 1.333333333,
  127. slow: 1.333333333,
  128. slowly: 1.333333333,
  129. faster: 1.333333333
  130. }
  131. /**
  132. * 按照xml进行减慢速度的计算
  133. * @param xml 始终按照第一分谱进行减慢速度的计算
  134. */
  135. export function getGradualLengthByXml(xml: string) {
  136. const firstPartXml = onlyVisible(xml, 0)
  137. const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
  138. const measures = Array.from(xmlParse.querySelectorAll('measure'))
  139. const notes = Array.from(xmlParse.querySelectorAll('note'))
  140. const words = Array.from(xmlParse.querySelectorAll('words'))
  141. const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
  142. const eles = []
  143. for (const ele of [...words, ...metronomes]) {
  144. const note = getNextNote(ele, 'direction')
  145. // console.log(ele, note)
  146. if (note) {
  147. const measure = note?.closest('measure')
  148. const measureNotes = Array.from(measure.querySelectorAll('note'))
  149. const noteInMeasureIndex = Array.from(measure.childNodes)
  150. .filter((item: any) => item.nodeName === 'note')
  151. .findIndex((item) => item === note)
  152. let allDuration = 0
  153. let leftDuration = 0
  154. for (let i = 0; i < measureNotes.length; i++) {
  155. const n: any = measureNotes[i]
  156. const duration = +(n.querySelector('duration')?.textContent || '0')
  157. allDuration += duration
  158. if (i < noteInMeasureIndex) {
  159. leftDuration = allDuration
  160. }
  161. }
  162. eles.push({
  163. ele,
  164. index: notes.indexOf(note),
  165. noteInMeasureIndex,
  166. textContent: ele.textContent,
  167. measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number')
  168. type: ele.tagName,
  169. allDuration,
  170. leftDuration
  171. })
  172. }
  173. }
  174. // 结尾处手动插入一个音符节点
  175. eles.push({
  176. ele: notes[notes.length - 1],
  177. index: notes.length,
  178. noteInMeasureIndex: 0,
  179. textContent: '',
  180. type: 'metronome',
  181. allDuration: 1,
  182. leftDuration: 1,
  183. measureIndex: measures.length
  184. })
  185. const gradualNotes: any[] = []
  186. eles.sort((a, b) => a.index - b.index)
  187. const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase())
  188. let isLastNoteAndNotClosed = false
  189. for (const ele of eles) {
  190. const textContent: any = ele.textContent?.toLocaleLowerCase().trim()
  191. if (ele === eles[eles.length - 1]) {
  192. if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
  193. isLastNoteAndNotClosed = true
  194. }
  195. }
  196. const isKeyWork = keys.find((k) => {
  197. const ks = k.split(' ')
  198. return textContent && ks.includes(textContent)
  199. })
  200. if (
  201. ele.type === 'metronome' ||
  202. (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) ||
  203. isLastNoteAndNotClosed
  204. ) {
  205. const indexOf = gradualNotes.findIndex((item) => item.length === 1)
  206. if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
  207. gradualNotes[indexOf][1] = {
  208. start: ele.index,
  209. measureIndex: ele.measureIndex,
  210. noteInMeasureIndex: ele.noteInMeasureIndex,
  211. allDuration: ele.allDuration,
  212. leftDuration: ele.leftDuration,
  213. type: textContent
  214. }
  215. }
  216. }
  217. if (ele.type === 'words' && isKeyWork) {
  218. gradualNotes.push([
  219. {
  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. }
  230. return gradualNotes
  231. }
  232. export default defineComponent({
  233. name: 'music-operation',
  234. props: {
  235. type: {
  236. type: String,
  237. default: 'add'
  238. },
  239. data: {
  240. type: Object as PropType<any>,
  241. default: () => {
  242. }
  243. },
  244. tagList: {
  245. type: Array as PropType<Array<SelectOption>>,
  246. default: () => []
  247. },
  248. subjectList: {
  249. type: Array as PropType<Array<SelectOption>>,
  250. default: () => []
  251. },
  252. musicSheetCategories: {
  253. type: Array as PropType<Array<SelectOption>>,
  254. default: () => []
  255. }
  256. },
  257. emits: ['close', 'getList'],
  258. setup(props, {slots, attrs, emit}) {
  259. const forms = reactive({
  260. graduals: {} as any, // 渐变速度
  261. playMode: 'MP3', // 播放类型
  262. xmlFileUrl: null, // XML
  263. midiUrl: null, // mid
  264. name: null, // 曲目名称
  265. // musicTag: [] as any, // 曲目标签
  266. composer: null, // 音乐人
  267. playSpeed: null as any, // 曲谱速度
  268. // showFingering: null as any, // 是否显示指法
  269. // canEvaluate: null as any, // 是否评测
  270. // notation: null as any, // 能否转和简谱
  271. // auditVersion: null as any, // 审核版本
  272. // sortNumber: null, // 排序
  273. musicCover: null, // 曲谱封面
  274. remark: null, // 曲谱描述
  275. musicSheetSoundList: [] as any, // 原音
  276. // musicSheetCategoriesId: null,
  277. status: false,
  278. musicSheetType: 'SINGLE', // 曲目类型
  279. sourceType: undefined, //来源类型/作者属性(PLATFORM: 平台; ORG: 机构; PERSON: 个人)
  280. // userId: null, // 所属人
  281. appAuditFlag: 0, // 是否审核版本
  282. midiFileUrl: null, // 伴奏文件 MIDI文件(保留字段)
  283. subjectIds: '', // 可用声部
  284. subjectIdList: [] as any, // 可用声部
  285. musicalInstrumentIdList: [] as any, //可用乐器
  286. musicCategoryId: null, //曲目分类
  287. musicSheetAccompanimentList: [] as any, //曲目伴奏
  288. audioType: 'HOMEMODE', // 伴奏类型
  289. isPlayBeat: true, // 是否播放节拍器
  290. isUseSystemBeat: true, // 是否使用系统节拍器(0:否;1:是)
  291. repeatedBeats: false, // 是否重复节拍时长
  292. evaluationStandard: 'FREQUENCY', // 评分标准 节奏 AMPLITUDE 音准 FREQUENCY 分贝 DECIBELS
  293. multiTracksSelection: [] as any, // 声轨
  294. musicSheetExtend: {} as any,//所属人信息
  295. })
  296. const state = reactive({
  297. loading: false,
  298. previewMode: false,//是否是预览模式
  299. tagList: [...props.tagList] as any, // 标签列表
  300. xmlFirstSpeed: null as any, // 第一个音轨速度
  301. partListNames: [] as any, // 所有音轨声部列表
  302. musicSheetCategories: [...props.musicSheetCategories] as any,
  303. musicSheetAccompanimentUrls: '' as any,
  304. musicSheetAccompanimentUrlList: [] as any,
  305. instrumentData: [],
  306. instrumentList: [],
  307. subjectList: [],
  308. showMusicSheetOwnerDialog: false, //所属人弹框
  309. // musicSheetOwnerData: {}, //所属人信息
  310. multiTracks: null,
  311. })
  312. const gradualData = reactive({
  313. list: [] as any[],
  314. gradualRefs: [] as any[]
  315. })
  316. const btnLoading = ref(false)
  317. const formsRef = ref()
  318. const message = useMessage()
  319. const dialog = useDialog()
  320. // 提交记录
  321. const onSubmit = async () => {
  322. formsRef.value.validate(async (error: any) => {
  323. console.log(error, 'error')
  324. if (error) {
  325. message.error(error[0]?.[0]?.message)
  326. return
  327. }
  328. try {
  329. //extConfigJson: {"repeatedBeats":0,"gradualTimes":{"75":"02:38:60","77":"02:43:39"}}
  330. const obj = {
  331. ...forms,
  332. musicTag: '-1',
  333. multiTracksSelection: forms.multiTracksSelection.join(','),
  334. musicSheetSoundList: forms.musicSheetSoundList.filter((next: any) => {
  335. return !!next.audioFileUrl;
  336. }),
  337. musicalInstrumentIds: forms.musicalInstrumentIdList.join(','),
  338. extConfigJson: JSON.stringify({gradualTimes: forms.graduals})
  339. }
  340. if (forms.audioType == 'MIDI') {
  341. obj.musicSheetSoundList = []
  342. }
  343. btnLoading.value = true
  344. if (props.type === 'add') {
  345. await musicSheetSave(obj)
  346. message.success('添加成功')
  347. } else if (props.type === 'edit') {
  348. await musicSheetUpdate({...obj, id: props.data.id})
  349. message.success('修改成功')
  350. }
  351. emit('getList')
  352. emit('close')
  353. } catch (e) {
  354. console.log(e)
  355. }
  356. setTimeout(() => {
  357. btnLoading.value = false
  358. }, 100)
  359. })
  360. }
  361. // 上传XML,初始化音轨 音轨速度 乐器、声部
  362. const readFileInputEventAsArrayBuffer = (file: any) => {
  363. const xmlRead = new FileReader()
  364. xmlRead.onload = (res) => {
  365. try {
  366. gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter(
  367. (item: any) => item.length === 2
  368. )
  369. } catch (error) {
  370. }
  371. state.partListNames = getPartListNames(res?.target?.result as any) as any
  372. // 这里是如果没有当前音轨就重新写
  373. for (let j = 0; j < state.partListNames.length; j++) {
  374. if (!forms.musicSheetSoundList[j]) {
  375. forms.musicSheetSoundList.push({audioFileUrl: null, track: null})
  376. }
  377. forms.musicSheetSoundList[j].track = state.partListNames[j].value
  378. }
  379. // 循环添加所在音轨的原音
  380. for (let index = forms.musicSheetSoundList.length; index < state.partListNames.length; index++) {
  381. const part = state.partListNames[index].value
  382. const sysData = {
  383. ...forms.musicSheetSoundList[0],
  384. track: part
  385. }
  386. if (!sysData.speed) {
  387. sysData.speed = state.xmlFirstSpeed
  388. }
  389. createSys(sysData)
  390. }
  391. if (forms.musicSheetSoundList.length == 0) {
  392. forms.musicSheetSoundList.push({audioFileUrl: '', track: ''})
  393. }
  394. }
  395. xmlRead.readAsText(file)
  396. }
  397. // 获取xml中所有轨道 乐器
  398. const getPartListNames = (xml: any) => {
  399. if (!xml) return []
  400. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  401. const partList =
  402. xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  403. const partListNames = Array.from(partList).map((item) => {
  404. const part = item.getElementsByTagName('part-name')?.[0].textContent || ''
  405. return {
  406. value: part,
  407. label: part
  408. }
  409. })
  410. if (partListNames.length > 0) {
  411. forms.musicSheetSoundList = forms.musicSheetSoundList.slice(0, partListNames.length)
  412. }
  413. state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
  414. if (!forms.playSpeed) {
  415. forms.playSpeed = Number.parseInt(state.xmlFirstSpeed)
  416. }
  417. // 乐器
  418. const instrumentCodeList: any = [];
  419. const instrumentEle = xmlParse.getElementsByTagName('virtual-instrument');
  420. for (let index = 0; index < instrumentEle.length; index++) {
  421. const note = instrumentEle[index]
  422. const instrumentCode = note.getElementsByTagName('virtual-name')?.[0].textContent || '';
  423. if (instrumentCode && !instrumentCodeList.includes(instrumentCode)) {
  424. instrumentCodeList.push(instrumentCode);
  425. }
  426. }
  427. const codeIdMap = new Map<string, string>();
  428. state.instrumentData.forEach((data: any) => {
  429. codeIdMap.set(data.code, data.id + '');
  430. })
  431. forms.musicalInstrumentIdList = [];
  432. instrumentCodeList.forEach((code: string) => {
  433. if (codeIdMap.has(code)) {
  434. forms.musicalInstrumentIdList.push(codeIdMap.get(code));
  435. }
  436. })
  437. // 声部
  438. if (forms.musicalInstrumentIdList.length > 0) {
  439. showBackSubject(forms.musicalInstrumentIdList);
  440. }
  441. return partListNames
  442. }
  443. // 判断选择的音轨是否在选中
  444. const initPartsListStatus = (track: string): any => {
  445. const _names = state.partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
  446. const partListNames = deepClone(_names) || []
  447. partListNames.forEach((item: any) => {
  448. const index = forms.musicSheetSoundList.findIndex((ground: any) => item.value == ground.track)
  449. if (index > -1 && track != item.value) {
  450. item.disabled = true
  451. } else {
  452. item.disabled = false
  453. }
  454. })
  455. return partListNames || []
  456. }
  457. // 获取乐器信息
  458. const initInstrumentList = async () => {
  459. if (state.instrumentList && state.instrumentList.length > 0) {
  460. return
  461. }
  462. try {
  463. const {data} = await musicalInstrumentPage({page: 1, rows: 999})
  464. const tempList = data.rows || []
  465. state.instrumentData = tempList
  466. tempList.forEach((item: any) => {
  467. item.label = item.name
  468. item.value = item.id + ''
  469. })
  470. state.instrumentList = tempList
  471. } catch {
  472. }
  473. }
  474. // 反显声部
  475. const showBackSubject = async (musicalInstrumentIdList: []) => {
  476. try {
  477. const {data} = await subjectPage({page: 1, rows: 999, musicalInstrumentIdList: musicalInstrumentIdList})
  478. const tempList = data.rows || []
  479. tempList.forEach((item: any) => {
  480. forms.subjectIdList.push(item.id + '')
  481. })
  482. } catch {
  483. }
  484. }
  485. // 添加原音
  486. const createSys = (initData?: any) => {
  487. forms.musicSheetSoundList.push({
  488. audioFileUrl: null, // 原音
  489. track: null, // 轨道
  490. ...initData
  491. })
  492. }
  493. // 删除原音
  494. const removeSys = (index: number) => {
  495. dialog.warning({
  496. title: '警告',
  497. content: `是否确认删除此原音?`,
  498. positiveText: '确定',
  499. negativeText: '取消',
  500. onPositiveClick: async () => {
  501. forms.musicSheetSoundList.splice(index, 1)
  502. }
  503. })
  504. }
  505. const checkMultiTracks = (value: string) => {
  506. if (!value) {
  507. return;
  508. }
  509. if (value === 'all') {
  510. forms.multiTracksSelection = []
  511. state.partListNames.forEach((next: any) => {
  512. forms.multiTracksSelection.push(next.value)
  513. })
  514. } else if (value === 'invert') {
  515. state.partListNames.forEach((next: any) => {
  516. const indexOf = forms.multiTracksSelection.indexOf(next.value);
  517. if (indexOf > -1) {
  518. forms.multiTracksSelection.splice(indexOf, 1)
  519. } else {
  520. forms.multiTracksSelection.push(next.value)
  521. }
  522. })
  523. } else if (value === 'allUncheck') {
  524. forms.multiTracksSelection = []
  525. }
  526. }
  527. onMounted(async () => {
  528. initInstrumentList();
  529. if (props.type === 'edit' || props.type === 'preview') {
  530. const detail = props.data
  531. try {
  532. state.loading = true
  533. if (props.type === 'preview') {
  534. state.previewMode = true
  535. }
  536. const {data} = await musicSheetDetail({id: detail.id})
  537. forms.audioType = data.audioType
  538. forms.playMode = data.playMode
  539. forms.xmlFileUrl = data.xmlFileUrl
  540. forms.midiUrl = data.midiUrl
  541. forms.name = data.name
  542. // forms.musicTag = data.musicTag?.split(',')
  543. forms.composer = data.composer
  544. forms.playSpeed = data.playSpeed
  545. // forms.showFingering = Number(data.showFingering)
  546. // forms.canEvaluate = Number(data.canEvaluate)
  547. // forms.notation = Number(data.notation)
  548. // forms.auditVersion = Number(data.auditVersion)
  549. // forms.sortNumber = data.sortNumber
  550. forms.musicCover = data.musicCover
  551. forms.remark = data.remark
  552. forms.status = data.status
  553. forms.musicCategoryId = data.musicCategoryId
  554. forms.musicSheetType = data.musicSheetType || "SINGLE"
  555. forms.musicSheetAccompanimentList = data.musicSheetAccompanimentList
  556. data.musicSheetAccompanimentList?.forEach((next: any) => {
  557. state.musicSheetAccompanimentUrlList.push(next.audioFileUrl);
  558. })
  559. forms.evaluationStandard = data.evaluationStandard
  560. forms.musicalInstrumentIdList = data.musicalInstrumentIds.split(',') || []
  561. forms.subjectIds = data.subjectIds
  562. forms.subjectIdList = data.subjectIds?.split(',') || []
  563. forms.sourceType = data.sourceType
  564. forms.musicSheetExtend = data.musicSheetExtend
  565. // 获取渐变 和 是否多声部
  566. try {
  567. const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
  568. forms.graduals = extConfigJson.gradualTimes || {}
  569. } catch (error) {
  570. }
  571. axios.get(data.xmlFileUrl).then((res: any) => {
  572. if (res?.data) {
  573. gradualData.list = getGradualLengthByXml(res?.data as any).filter(
  574. (item: any) => item.length === 2
  575. )
  576. state.partListNames = getPartListNames(res?.data as any) as any
  577. // 初始化音轨和原音
  578. forms.musicSheetSoundList = data.musicSheetSoundList || []
  579. forms.musicSheetSoundList.forEach((next: any) => {
  580. forms.multiTracksSelection.push(next.track)
  581. })
  582. state.partListNames.forEach((item: any) => {
  583. const tracks = forms.musicSheetSoundList.map((item: any) => item.track);
  584. if (tracks.indexOf(item.label) <= -1) {
  585. forms.musicSheetSoundList.push({audioFileUrl: '', track: item.label})
  586. }
  587. })
  588. }
  589. })
  590. } catch (error) {
  591. console.log(error)
  592. }
  593. state.loading = false
  594. }
  595. })
  596. return () => (
  597. <div style="background: #fff; padding-top: 12px">
  598. <NForm
  599. class={styles.formContainer}
  600. model={forms}
  601. ref={formsRef}
  602. label-placement="left"
  603. label-width="130"
  604. disabled={state.previewMode}
  605. >
  606. <NAlert showIcon={false} style={{marginBottom: "12px"}}>曲目信息</NAlert>
  607. <NGrid cols={2}>
  608. <NFormItemGi
  609. label="曲目名称"
  610. path="name"
  611. rule={[
  612. {
  613. required: true,
  614. message: '请输入曲目名称'
  615. }
  616. ]}
  617. >
  618. <NInput
  619. v-model:value={forms.name}
  620. placeholder="请输入曲目名称"
  621. maxlength={120}
  622. showCount
  623. />
  624. </NFormItemGi>
  625. <NFormItemGi
  626. label="音乐人"
  627. path="composer"
  628. rule={[
  629. {
  630. required: true,
  631. message: '请输入音乐人'
  632. }
  633. ]}
  634. >
  635. <NInput v-model:value={forms.composer}
  636. placeholder="请输入音乐人名称"
  637. showCount
  638. maxlength={14}
  639. />
  640. </NFormItemGi>
  641. </NGrid>
  642. <NGrid cols={2}>
  643. <NFormItemGi label="曲目描述" path="remark">
  644. <NInput
  645. placeholder="请输入曲目描述"
  646. type="textarea"
  647. rows={4}
  648. showCount
  649. maxlength={200}
  650. v-model:value={forms.remark}
  651. />
  652. </NFormItemGi>
  653. <NFormItemGi label="曲目封面" path="musicCover"
  654. rule={[
  655. {
  656. required: true
  657. }
  658. ]}>
  659. <UploadFile
  660. disabled={state.previewMode}
  661. accept=".jpg,.jpeg,.png"
  662. tips="请上传大小1M以内的JPG、PNG图片"
  663. size={1}
  664. v-model:fileList={forms.musicCover}
  665. cropper
  666. bucketName="cbs"
  667. options={{
  668. autoCrop: true, //是否默认生成截图框
  669. enlarge: 2, // 图片放大倍数
  670. autoCropWidth: 200, //默框高度
  671. fixedBox: true, //是否固定截图框大认生成截图框宽度
  672. autoCropHeight: 200, //默认生成截图小 不允许改变
  673. previewsCircle: false, //预览图是否是原圆形
  674. title: '曲目封面'
  675. }}
  676. />
  677. </NFormItemGi>
  678. </NGrid>
  679. <NGrid cols={2}>
  680. <NFormItemGi
  681. label="曲目类型"
  682. path="musicSheetType"
  683. rule={[
  684. {
  685. required: true,
  686. message: '请选择曲目类型'
  687. }
  688. ]}
  689. >
  690. <NSelect
  691. placeholder="请选择曲目类型"
  692. v-model:value={forms.musicSheetType}
  693. options={getSelectDataFromObj(musicSheetType)}
  694. />
  695. </NFormItemGi>
  696. <NFormItemGi
  697. label="作者属性"
  698. path="sourceType"
  699. rule={[
  700. {
  701. required: true,
  702. message: '请选择作者属性'
  703. }
  704. ]}
  705. >
  706. <NSelect
  707. v-model:value={forms.sourceType}
  708. options={getSelectDataFromObj(musicSheetSourceType)}
  709. placeholder="请选择作者属性"
  710. onUpdateValue={() => {
  711. // 发送变化,清理选择的所属人信息
  712. forms.musicSheetExtend = {}
  713. // forms.musicSheetExtend.userId = null
  714. // forms.musicSheetExtend.userName = null
  715. // forms.musicSheetExtend.applicationId = null
  716. // forms.musicSheetExtend.organizationRoleId = null
  717. }}
  718. />
  719. </NFormItemGi>
  720. </NGrid>
  721. <NGrid cols={2}>
  722. <NFormItemGi
  723. label="所属人"
  724. path="musicSheetExtend.userId"
  725. rule={[
  726. {
  727. required: true,
  728. message: '请选择曲目所属人'
  729. }
  730. ]}
  731. >
  732. <NButton
  733. disabled={state.previewMode || !forms.sourceType}
  734. type="primary"
  735. size="small"
  736. text
  737. //v-auth="orchestraSubsidyStandard/update1597887579789053953"
  738. onClick={() => {
  739. state.showMusicSheetOwnerDialog = true
  740. }}
  741. >
  742. {forms.musicSheetExtend?.userId ? forms.musicSheetExtend.userName + "(" + forms.musicSheetExtend.userId + ")" : '请选择所属人'}
  743. </NButton>
  744. </NFormItemGi>
  745. <NFormItemGi label="速度" path="playSpeed">
  746. <NInputNumber
  747. placeholder="请输入速度"
  748. v-model:value={forms.playSpeed}
  749. style="width:100%"
  750. />
  751. </NFormItemGi>
  752. </NGrid>
  753. <NGrid cols={2}>
  754. <NFormItemGi label="审核版本" path="appAuditFlag"
  755. rule={[
  756. {
  757. required: true,
  758. message: '请选择曲目所属人'
  759. }
  760. ]}
  761. >
  762. <NSelect
  763. options={
  764. [
  765. {
  766. label: '是',
  767. value: 1
  768. },
  769. {
  770. label: '否',
  771. value: 0
  772. }
  773. ] as any
  774. }
  775. v-model:value={forms.appAuditFlag}
  776. />
  777. </NFormItemGi>
  778. <NFormItemGi label="曲目分类" path="musicCategoryId"
  779. rule={[
  780. {
  781. required: true,
  782. message: '请选择曲目分类'
  783. }
  784. ]}
  785. >
  786. <NCascader
  787. valueField="id"
  788. labelField="name"
  789. children-field="musicSheetCategoriesList"
  790. placeholder="请选择分类"
  791. v-model:value={forms.musicCategoryId}
  792. options={state.musicSheetCategories}
  793. clearable
  794. />
  795. </NFormItemGi>
  796. </NGrid>
  797. <NGrid cols={2}>
  798. <NFormItemGi label="是否重复节拍时长" path="repeatedBeats"
  799. rule={[
  800. {
  801. required: true,
  802. message: '请选择是否重复节拍时长'
  803. }
  804. ]}
  805. >
  806. <NRadioGroup
  807. v-model:value={forms.repeatedBeats}
  808. >
  809. <NRadio value={true}>是</NRadio>
  810. <NRadio value={false}>否</NRadio>
  811. </NRadioGroup>
  812. </NFormItemGi>
  813. <NFormItemGi
  814. label="评分标准"
  815. path="evaluationStandard"
  816. rule={[
  817. {
  818. required: true
  819. }
  820. ]}
  821. >
  822. <NRadioGroup
  823. v-model:value={forms.evaluationStandard}
  824. >
  825. <NRadio value={'FREQUENCY'}>标准评测</NRadio>
  826. <NRadio value={'AMPLITUDE'}>打击乐(振幅)</NRadio>
  827. <NRadio value={'DECIBELS'}>节奏(分贝)</NRadio>
  828. </NRadioGroup>
  829. </NFormItemGi>
  830. </NGrid>
  831. <NAlert showIcon={false} style={{marginBottom: "12px"}}>曲目上传</NAlert>
  832. <NGrid cols={2}>
  833. <NFormItemGi label="播放模式" path="playMode"
  834. rule={[
  835. {
  836. required: true,
  837. message: '请选择播放模式'
  838. }
  839. ]}
  840. >
  841. <NRadioGroup
  842. v-model:value={forms.playMode}
  843. onUpdateValue={(value: string | number | boolean) => {
  844. if (value === 'MP3') {
  845. forms.playMode = 'MP3'
  846. } else {
  847. forms.playMode = 'MIDI'
  848. }
  849. }}
  850. >
  851. <NRadio value="MP3">MP3</NRadio>
  852. <NRadio value="MIDI">MIDI</NRadio>
  853. </NRadioGroup>
  854. </NFormItemGi>
  855. {forms.playMode === 'MP3' && (
  856. <NFormItemGi
  857. label="伴奏类型"
  858. path="audioType"
  859. rule={[
  860. {
  861. required: true
  862. }
  863. ]}
  864. >
  865. <NRadioGroup
  866. v-model:value={forms.audioType}
  867. >
  868. <NRadio value={'HOMEMODE'}>自制伴奏</NRadio>
  869. <NRadio value={'COMMON'}>普通伴奏</NRadio>
  870. </NRadioGroup>
  871. </NFormItemGi>
  872. )}
  873. </NGrid>
  874. <NGrid cols={2}>
  875. {forms.playMode === 'MP3' && (
  876. <NFormItemGi
  877. label="上传伴奏"
  878. path="musicSheetAccompanimentList"
  879. rule={[
  880. {
  881. required: true,
  882. message: '请选择上传.mp3'
  883. }
  884. ]}
  885. >
  886. <UploadFile
  887. disabled={state.previewMode}
  888. size={10}
  889. v-model:imageList={state.musicSheetAccompanimentUrlList}
  890. tips="仅支持上传.mp3格式文件"
  891. listType="image"
  892. accept=".mp3"
  893. bucketName="cloud-coach"
  894. text="点击上传伴奏文件"
  895. max={10}
  896. onUpload:success={(file) => {
  897. state.musicSheetAccompanimentUrls = [state.musicSheetAccompanimentUrls, file.url].filter(Boolean).join(',')
  898. state.musicSheetAccompanimentUrlList = state.musicSheetAccompanimentUrls?.split(',').filter(Boolean)
  899. forms.musicSheetAccompanimentList = []
  900. for (let i = 0; i < state.musicSheetAccompanimentUrlList.length; i++) {
  901. forms.musicSheetAccompanimentList.push({
  902. audioFileUrl: state.musicSheetAccompanimentUrlList[i],
  903. track: file.name,
  904. sortNumber: i + 1
  905. })
  906. }
  907. }}
  908. onRemove={() => {
  909. state.musicSheetAccompanimentUrlList = []
  910. state.musicSheetAccompanimentUrls = ''
  911. }}
  912. // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  913. multiple={true}
  914. />
  915. </NFormItemGi>
  916. )}
  917. {forms.playMode === 'MIDI' && (
  918. <NFormItemGi
  919. label="上传MIDI"
  920. path="midiFileUrl"
  921. rule={[
  922. {
  923. required: true,
  924. message: '请选择上传.MIDI格式文件'
  925. }
  926. ]}
  927. >
  928. <UploadFile
  929. disabled={state.previewMode}
  930. size={10}
  931. v-model:fileList={forms.midiFileUrl}
  932. tips="仅支持上传.MIDI格式文件"
  933. listType="image"
  934. accept=".mp3,.aac"
  935. bucketName="cloud-coach"
  936. text="点击上传MIDI文件"
  937. // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  938. />
  939. </NFormItemGi>
  940. )}
  941. <NFormItemGi
  942. label="上传XML"
  943. path="xmlFileUrl"
  944. rule={[
  945. {
  946. required: true,
  947. message: '请选择上传XML'
  948. }
  949. ]}
  950. >
  951. <UploadFile
  952. disabled={state.previewMode}
  953. size={10}
  954. v-model:fileList={forms.xmlFileUrl}
  955. tips="仅支持上传.xml格式文件"
  956. listType="image"
  957. accept=".xml"
  958. bucketName="cloud-coach"
  959. text="点击上传XML文件"
  960. onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  961. onRemove={() => {
  962. forms.multiTracksSelection = []
  963. state.partListNames = []
  964. forms.musicSheetSoundList = []
  965. }}
  966. />
  967. </NFormItemGi>
  968. </NGrid>
  969. <NGrid cols={2}>
  970. <NFormItemGi label="可用声部" path="subjectIdList"
  971. rule={[
  972. {
  973. required: true,
  974. message: '请选择可用声部'
  975. }
  976. ]}
  977. >
  978. <NSelect
  979. v-model:value={forms.subjectIdList}
  980. options={props.subjectList}
  981. multiple
  982. filterable
  983. clearable
  984. placeholder="请选择可用声部"
  985. />
  986. </NFormItemGi>
  987. <NFormItemGi label="可用乐器" path="musicalInstrumentIdList"
  988. rule={[
  989. {
  990. required: true,
  991. message: '请选择可用乐器'
  992. }
  993. ]}
  994. >
  995. <NSelect
  996. placeholder="请选择可用乐器"
  997. options={state.instrumentList}
  998. v-model:value={forms.musicalInstrumentIdList}
  999. clearable
  1000. multiple
  1001. maxTagCount={2}
  1002. />
  1003. </NFormItemGi>
  1004. </NGrid>
  1005. {(forms.musicSheetType) && (
  1006. <NGrid cols={1}>
  1007. <NFormItemGi
  1008. label={`${forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'}`}
  1009. path="multiTracksSelection"
  1010. rule={[
  1011. {
  1012. required: true,
  1013. message: `请选择${forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'}`
  1014. }
  1015. ]}
  1016. >
  1017. <NGrid style="padding-top: 4px;">
  1018. <NGi span={24}>
  1019. <NRadioGroup
  1020. v-model:value={state.multiTracks}
  1021. onUpdateValue={(value) => {
  1022. checkMultiTracks(value)
  1023. }}
  1024. >
  1025. <NRadio value={'all'}>全选</NRadio>
  1026. <NRadio value={'allUncheck'}>重置</NRadio>
  1027. <NRadio value={'invert'}>反选</NRadio>
  1028. </NRadioGroup>
  1029. </NGi>
  1030. <NGi span={24} style={"margin-top:5px"}><NFormItemGi
  1031. label=''
  1032. path="multiTracksSelection"
  1033. rule={[
  1034. {
  1035. required: false,
  1036. }
  1037. ]}
  1038. >
  1039. <NCheckboxGroup
  1040. v-model:value={forms.multiTracksSelection}
  1041. >
  1042. <NGrid yGap={2} cols={4}
  1043. >
  1044. {state.partListNames.map((item: any, index: number) => (
  1045. <NGi>
  1046. <NCheckbox value={item.value} label={item.label} onUpdateChecked={() => {
  1047. console.log("forms.multiTracksSelection", forms.multiTracksSelection)
  1048. console.log("forms.musicSheetSoundList", forms.musicSheetSoundList)
  1049. }}/>
  1050. </NGi>
  1051. ))}
  1052. </NGrid>
  1053. </NCheckboxGroup>
  1054. </NFormItemGi></NGi>
  1055. </NGrid>
  1056. </NFormItemGi>
  1057. </NGrid>
  1058. )
  1059. }
  1060. <NGrid cols={2}>
  1061. <NFormItemGi label="是否播放节拍器" path="isPlayBeat"
  1062. rule={[
  1063. {
  1064. required: true,
  1065. message: '请选择是否播放节拍器'
  1066. }
  1067. ]}
  1068. >
  1069. <NRadioGroup
  1070. v-model:value={forms.isPlayBeat}
  1071. >
  1072. <NRadio value={true}>是</NRadio>
  1073. <NRadio value={false}>否</NRadio>
  1074. </NRadioGroup>
  1075. </NFormItemGi>
  1076. {forms.isPlayBeat && (
  1077. <NFormItemGi label="播放方式" path="audioType"
  1078. rule={[
  1079. {
  1080. required: true,
  1081. message: '请选择播放方式'
  1082. }
  1083. ]}
  1084. >
  1085. <NRadioGroup
  1086. v-model:value={forms.isUseSystemBeat}
  1087. >
  1088. <NRadio value={true}>系统节拍器</NRadio>
  1089. <NRadio value={false}>MP3节拍器</NRadio>
  1090. </NRadioGroup>
  1091. </NFormItemGi>
  1092. )}
  1093. </NGrid>
  1094. {/* 只有播放类型为mp3时才会有原音 */}
  1095. {forms.playMode === 'MP3' && forms.musicSheetSoundList.length > 0 && (
  1096. <>
  1097. {forms.musicSheetSoundList.map((item: any, index: number) => (
  1098. <>
  1099. {item.track?.toLocaleUpperCase?.() != 'COMMON' && forms.multiTracksSelection.indexOf(item.track) > -1 &&
  1100. <NGrid class={styles.audioSection}
  1101. // v-show={forms.multiTracksSelection.indexOf(item.track) > -1}
  1102. >
  1103. <NFormItemGi
  1104. span={12}
  1105. label="原音"
  1106. path={`musicSheetSoundList[${index}].audioFileUrl`}
  1107. rule={[
  1108. {
  1109. // required: forms.multiTracksSelection.indexOf(forms.musicSheetSoundList[index].audioFileUrl) > -1,
  1110. required: true,
  1111. message: `请上传${
  1112. item.track ? item.track + '的' : '第' + (index + 1) + '个'
  1113. }原音`
  1114. }
  1115. ]}
  1116. >
  1117. <UploadFile
  1118. disabled={state.previewMode}
  1119. size={10}
  1120. v-model:fileList={item.audioFileUrl}
  1121. tips="仅支持上传.mp3/.aac格式文件"
  1122. listType="image"
  1123. accept=".mp3,.aac"
  1124. bucketName="cloud-coach"
  1125. />
  1126. </NFormItemGi>
  1127. {state.partListNames.length > 1 && (
  1128. <NFormItemGi
  1129. span={12}
  1130. label="所属轨道"
  1131. path={`musicSheetSoundList[${index}].track`}
  1132. rule={[
  1133. {
  1134. required: true,
  1135. message: '请选择所属轨道'
  1136. }
  1137. ]}
  1138. >
  1139. <NSelect
  1140. placeholder="请选择所属轨道"
  1141. v-model:value={item.track}
  1142. options={initPartsListStatus(item.track)}
  1143. />
  1144. </NFormItemGi>
  1145. )}
  1146. </NGrid>}
  1147. </>
  1148. ))}
  1149. <NButton
  1150. type="primary"
  1151. dashed
  1152. block
  1153. disabled={state.partListNames.length <= forms.musicSheetSoundList.length}
  1154. style={{
  1155. marginBottom: '24px'
  1156. }}
  1157. onClick={createSys}
  1158. >
  1159. 添加原音
  1160. </NButton>
  1161. </>
  1162. )}
  1163. </NForm>
  1164. {props.type !== 'preview' &&
  1165. (
  1166. <NSpace justify="end" style="padding-top:12px">
  1167. <NButton type="default" onClick={() => emit('close')}>
  1168. 取消
  1169. </NButton>
  1170. <NButton
  1171. type="primary"
  1172. onClick={() => onSubmit()}
  1173. loading={btnLoading.value}
  1174. disabled={btnLoading.value}
  1175. >
  1176. 确认
  1177. </NButton>
  1178. </NSpace>
  1179. )}
  1180. <NModal
  1181. v-model:show={state.showMusicSheetOwnerDialog}
  1182. preset="dialog"
  1183. showIcon={false}
  1184. maskClosable={false}
  1185. title="所属人"
  1186. style={{width: '800px'}}
  1187. >
  1188. <MusicSheetOwnerDialog
  1189. sourceType={forms.sourceType}
  1190. onClose={() => {
  1191. state.showMusicSheetOwnerDialog = false
  1192. }}
  1193. onChoseMusicSheetOwnerData={(musicSheetOwnerData) => {
  1194. forms.musicSheetExtend = {
  1195. ...musicSheetOwnerData
  1196. }
  1197. console.log(forms.musicSheetExtend)
  1198. }}
  1199. />
  1200. </NModal>
  1201. </div>
  1202. )
  1203. }
  1204. })