music-operation.tsx 52 KB

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