music-operation.tsx 51 KB

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