music-operation.tsx 54 KB

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