music-operation.tsx 58 KB

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