music-operation.tsx 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620
  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, []>() as any
  445. state.instrumentData.forEach((data: any) => {
  446. const codes = data.code.split(/,|,/)
  447. codes.forEach((code: string) => {
  448. if (codeIdMap.has(code)) {
  449. codeIdMap.get(code).push(data.id + '')
  450. } else {
  451. const arr = [] as any;
  452. arr.push(data.id + '')
  453. codeIdMap.set(code, arr)
  454. }
  455. // codeIdMap.set(code, data.id + '')
  456. })
  457. })
  458. forms.musicalInstrumentIdList = []
  459. instrumentCodeList.forEach((code: string) => {
  460. if (codeIdMap.has(code)) {
  461. codeIdMap.get(code).forEach((c: any) => {
  462. forms.musicalInstrumentIdList.push(c)
  463. })
  464. }
  465. })
  466. // 声部
  467. if (forms.musicalInstrumentIdList.length > 0) {
  468. showBackSubject(forms.musicalInstrumentIdList)
  469. }
  470. }
  471. // 获取xml中所有轨道 乐器
  472. const getPartListNames = (xml: any) => {
  473. if (!xml) return []
  474. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  475. const partList =
  476. xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  477. let partListNames = Array.from(partList).map((item) => {
  478. const part = item.getElementsByTagName('part-name')?.[0].textContent || ''
  479. return {
  480. value: part,
  481. label: part
  482. }
  483. })
  484. partListNames = partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
  485. if (partListNames.length > 0) {
  486. forms.musicSheetSoundList = forms.musicSheetSoundList.slice(0, partListNames.length)
  487. }
  488. state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
  489. if (!forms.playSpeed) {
  490. if (state.xmlFirstSpeed) {
  491. forms.playSpeed = Number.parseInt(state.xmlFirstSpeed)
  492. } else {
  493. // 速度默认给100
  494. forms.playSpeed = 100
  495. }
  496. }
  497. return partListNames
  498. }
  499. // 判断选择的音轨是否在选中
  500. const initPartsListStatus = (track: string): any => {
  501. // const _names = state.partListNames.filter(
  502. // (n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON'
  503. // )
  504. const partListNames = deepClone(state.partListNames) || []
  505. const multiTracksSelection = forms.multiTracksSelection;
  506. partListNames.forEach((item: any) => {
  507. if (multiTracksSelection.includes(item.value)) {
  508. item.disabled = true
  509. } else {
  510. item.disabled = false
  511. }
  512. // const index = forms.musicSheetSoundList.findIndex(
  513. // (ground: any) => item.value == ground.track
  514. // )
  515. // if (index > -1 && track == item.value) {
  516. // item.disabled = false
  517. // } else {
  518. // item.disabled = true
  519. // }
  520. })
  521. return partListNames || []
  522. }
  523. // 反显声部
  524. const showBackSubject = async (musicalInstrumentIdList: []) => {
  525. try {
  526. const { data } = await subjectPage({
  527. page: 1,
  528. rows: 999,
  529. musicalInstrumentIdList: musicalInstrumentIdList
  530. })
  531. const tempList = data.rows || []
  532. tempList.forEach((item: any) => {
  533. forms.subjectIds.push(item.id + '')
  534. })
  535. } catch {}
  536. }
  537. // 添加原音
  538. const createSys = (initData?: any) => {
  539. forms.musicSheetSoundList.push({
  540. audioFileUrl: null, // 原音
  541. track: null, // 轨道
  542. ...initData
  543. })
  544. }
  545. // 删除原音
  546. const removeSys = (index: number) => {
  547. dialog.warning({
  548. title: '提示',
  549. content: `是否确认删除此原音?`,
  550. positiveText: '确定',
  551. negativeText: '取消',
  552. onPositiveClick: async () => {
  553. const sound = forms.musicSheetSoundList[index];
  554. const track = sound.track
  555. if (track) {
  556. const selectIndex = forms.multiTracksSelection.indexOf(track)
  557. if (selectIndex > -1) {
  558. forms.multiTracksSelection.splice(selectIndex, 1)
  559. }
  560. } else {
  561. forms.musicSheetSoundList.splice(index, 1)
  562. }
  563. }
  564. })
  565. }
  566. const checkMultiTracks = (value: string) => {
  567. if (!value) {
  568. return
  569. }
  570. if (value === 'all') {
  571. forms.multiTracksSelection = []
  572. state.partListNames.forEach((next: any) => {
  573. forms.multiTracksSelection.push(next.value)
  574. })
  575. } else if (value === 'invert') {
  576. state.partListNames.forEach((next: any) => {
  577. const indexOf = forms.multiTracksSelection.indexOf(next.value)
  578. if (indexOf > -1) {
  579. forms.multiTracksSelection.splice(indexOf, 1)
  580. } else {
  581. forms.multiTracksSelection.push(next.value)
  582. }
  583. })
  584. } else if (value === 'allUncheck') {
  585. forms.multiTracksSelection = []
  586. }
  587. }
  588. const setOwnerName = () => {
  589. if (forms.sourceType == 'PLATFORM') {
  590. state.ownerName = ''
  591. return
  592. }
  593. if (!forms.musicSheetExtend || !forms.sourceType || !forms.musicSheetExtend?.userId) {
  594. return
  595. }
  596. const appId = forms.musicSheetExtend.applicationId
  597. const app = state.appData.filter((next: any) => {
  598. return next.id == appId
  599. }) as any
  600. if (app.length > 0) {
  601. state.ownerName = app[0].appName
  602. }
  603. if (forms.sourceType == 'ORG') {
  604. state.ownerName += '-' + forms.musicSheetExtend.organizationRole
  605. } else if (forms.sourceType == 'PERSON') {
  606. state.ownerName +=
  607. '-' +
  608. getMapValueByKey(forms.musicSheetExtend.clientType, new Map(Object.entries(clientType)))
  609. if (forms.musicSheetExtend.userName) {
  610. state.ownerName += '-' + forms.musicSheetExtend.userName
  611. }
  612. if (forms.musicSheetExtend.phone) {
  613. state.ownerName += '(' + forms.musicSheetExtend.phone + ')'
  614. }
  615. }
  616. }
  617. onMounted(async () => {
  618. state.loading = true
  619. if (props.type === 'preview') {
  620. state.previewMode = true
  621. }
  622. // 获取乐器信息
  623. {
  624. if (state.instrumentList && state.instrumentList.length > 0) {
  625. return
  626. }
  627. try {
  628. const { data } = await musicalInstrumentPage({ page: 1, rows: 999 })
  629. const tempList = data.rows || []
  630. state.instrumentData = tempList
  631. tempList.forEach((item: any) => {
  632. item.label = item.name
  633. item.value = item.id + ''
  634. item.disabled = !item.enableFlag
  635. })
  636. state.instrumentList = tempList
  637. } catch {}
  638. }
  639. state.subjectList = deepClone(props.subjectList)
  640. state.subjectList.forEach((subject: any) => {
  641. subject.disabled = !subject.enableFlag
  642. })
  643. // 初始化应用
  644. {
  645. const appKeys = Object.keys(appKey)
  646. const { data } = await sysApplicationPage({ page: 1, rows: 999, parentId: 0 })
  647. const tempList = data.rows || []
  648. const filter = tempList.filter((next: any) => {
  649. return appKeys.includes(next.appKey)
  650. })
  651. filter.forEach((item: any) => {
  652. item.label = item.appName
  653. item.value = item.id
  654. })
  655. state.appData = filter
  656. }
  657. // 获取分类信息
  658. {
  659. try {
  660. const { data } = await musicSheetCategoriesQueryTree({ enable: true })
  661. state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList')
  662. } catch (e) {}
  663. }
  664. if (props.type === 'edit' || props.type === 'preview') {
  665. const detail = props.data
  666. try {
  667. const { data } = await musicSheetDetail({ id: detail.id })
  668. forms.playMode = data.playMode
  669. forms.xmlFileUrl = data.xmlFileUrl
  670. forms.midiUrl = data.midiUrl
  671. forms.name = data.name
  672. // forms.musicTag = data.musicTag?.split(',')
  673. forms.composer = data.composer
  674. forms.playSpeed = data.playSpeed
  675. // forms.showFingering = Number(data.showFingering)
  676. // forms.canEvaluate = Number(data.canEvaluate)
  677. // forms.notation = Number(data.notation)
  678. // forms.auditVersion = Number(data.auditVersion)
  679. // forms.sortNumber = data.sortNumber
  680. forms.musicCover = data.musicCover
  681. forms.remark = data.remark
  682. forms.status = data.status
  683. forms.musicSheetType = data.musicSheetType || 'SINGLE'
  684. forms.sourceType = data.sourceType
  685. forms.appAuditFlag = data.appAuditFlag ? 1 : 0
  686. forms.midiFileUrl = data.midiFileUrl
  687. forms.subjectIds = []
  688. if (data.subjectIds) {
  689. const subjectIds = data.subjectIds.split(',') || []
  690. subjectIds.forEach((subjectId: any) => {
  691. if (!forms.subjectIds.includes(subjectId)) {
  692. forms.subjectIds.push(subjectId)
  693. }
  694. })
  695. }
  696. forms.musicalInstrumentIdList = data.musicalInstrumentIds ? data.musicalInstrumentIds.split(',') : []
  697. forms.musicCategoryId = data.musicCategoryId
  698. data.musicSheetAccompanimentList?.forEach((next: any) => {
  699. state.musicSheetAccompanimentUrlList.push(next.audioFileUrl)
  700. })
  701. forms.musicSheetAccompanimentList = data.musicSheetAccompanimentList
  702. forms.audioType = data.audioType
  703. forms.isPlayBeat = data.isPlayBeat
  704. forms.isUseSystemBeat = data.isUseSystemBeat
  705. // 获取渐变 和 是否多声部
  706. try {
  707. const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
  708. forms.graduals = extConfigJson.gradualTimes || {}
  709. forms.repeatedBeats = !!extConfigJson.repeatedBeats
  710. } catch (error) {}
  711. forms.evaluationStandard = data.evaluationStandard
  712. forms.musicSheetExtend = data.musicSheetExtend
  713. setOwnerName()
  714. axios.get(data.xmlFileUrl).then((res: any) => {
  715. if (res?.data) {
  716. gradualData.list = getGradualLengthByXml(res?.data as any).filter(
  717. (item: any) => item.length === 2
  718. )
  719. state.partListNames = getPartListNames(res?.data as any) as any
  720. // 初始化音轨和原音
  721. forms.multiTracksSelection = data.multiTracksSelection ? data.multiTracksSelection.split(',') : []
  722. const existSoundList = data.musicSheetSoundList ? data.musicSheetSoundList : []
  723. const tracks = [] as any
  724. state.partListNames.forEach((item: any) => {
  725. let audioFileUrl = null
  726. existSoundList.forEach((next: any) => {
  727. if (next.track == item.value) {
  728. audioFileUrl = next.audioFileUrl
  729. }
  730. })
  731. forms.musicSheetSoundList.push({
  732. audioFileUrl: audioFileUrl, // 原音
  733. track: item.value // 轨道
  734. })
  735. tracks.push(item.value)
  736. })
  737. // 处理没有声轨,但有原音
  738. existSoundList.filter((next: any) => {
  739. return !tracks.includes(next.track)
  740. }).forEach((next: any) => {
  741. forms.musicSheetSoundList.push({
  742. audioFileUrl: next.audioFileUrl, // 原音
  743. track: next.track ? next.track : null // 轨道
  744. })
  745. })
  746. }
  747. })
  748. } catch (error) {}
  749. } else {
  750. // 新增只能使用启用状态的数据
  751. state.subjectList = state.subjectList.filter((next: any) => {
  752. return next.enableFlag == true
  753. })
  754. state.instrumentList = state.instrumentList.filter((next: any) => {
  755. return next.enableFlag == true
  756. })
  757. }
  758. state.loading = false
  759. })
  760. return () => (
  761. <div style="background: #fff; padding-top: 12px">
  762. <NSpin show={state.loading}>
  763. <NForm
  764. class={styles.formContainer}
  765. model={forms}
  766. ref={formsRef}
  767. label-placement="left"
  768. label-width="150"
  769. disabled={state.previewMode}
  770. >
  771. <NAlert showIcon={false} style={{ marginBottom: '12px' }}>
  772. 曲目信息
  773. </NAlert>
  774. <NGrid cols={2}>
  775. <NFormItemGi
  776. label="曲目名称"
  777. path="name"
  778. rule={[
  779. {
  780. required: true,
  781. message: '请输入曲目名称',
  782. trigger: ['input', 'blur']
  783. }
  784. ]}
  785. >
  786. <NInput
  787. v-model:value={forms.name}
  788. placeholder="请输入曲目名称"
  789. maxlength={50}
  790. showCount
  791. />
  792. </NFormItemGi>
  793. <NFormItemGi
  794. label="音乐人"
  795. path="composer"
  796. rule={[
  797. {
  798. required: true,
  799. message: '请输入音乐人',
  800. trigger: ['input', 'blur']
  801. }
  802. ]}
  803. >
  804. <NInput
  805. v-model:value={forms.composer}
  806. placeholder="请输入音乐人名称"
  807. showCount
  808. maxlength={14}
  809. />
  810. </NFormItemGi>
  811. </NGrid>
  812. <NGrid cols={2}>
  813. <NFormItemGi label="曲目描述" path="remark">
  814. <NInput
  815. placeholder="请输入曲目描述"
  816. type="textarea"
  817. rows={4}
  818. showCount
  819. maxlength={200}
  820. v-model:value={forms.remark}
  821. />
  822. </NFormItemGi>
  823. <NFormItemGi
  824. label="曲目封面"
  825. path="musicCover"
  826. rule={[
  827. {
  828. required: true,
  829. message: '请上传曲目封面',
  830. trigger: ['input', 'blur']
  831. }
  832. ]}
  833. >
  834. <UploadFile
  835. desc={'封面图'}
  836. disabled={state.previewMode}
  837. accept=".jpg,.jpeg,.png"
  838. tips="请上传大小1M以内的JPG、PNG图片"
  839. size={1}
  840. v-model:fileList={forms.musicCover}
  841. cropper
  842. bucketName="cbs"
  843. options={{
  844. autoCrop: true, //是否默认生成截图框
  845. enlarge: 2, // 图片放大倍数
  846. autoCropWidth: 200, //默框高度
  847. fixedBox: true, //是否固定截图框大认生成截图框宽度
  848. autoCropHeight: 200, //默认生成截图小 不允许改变
  849. previewsCircle: false, //预览图是否是原圆形
  850. title: '曲目封面'
  851. }}
  852. />
  853. </NFormItemGi>
  854. </NGrid>
  855. <NGrid cols={2}>
  856. <NFormItemGi
  857. label="作者属性"
  858. path="sourceType"
  859. rule={[
  860. {
  861. required: true,
  862. message: '请选择作者属性',
  863. trigger: 'change'
  864. }
  865. ]}
  866. >
  867. <NSelect
  868. v-model:value={forms.sourceType}
  869. options={getSelectDataFromObj(musicSheetSourceType)}
  870. placeholder="请选择作者属性"
  871. onUpdateValue={() => {
  872. // 发送变化,清理选择的所属人信息
  873. forms.musicSheetExtend = {}
  874. state.ownerName = null
  875. // forms.musicSheetExtend.userId = null
  876. // forms.musicSheetExtend.userName = null
  877. // forms.musicSheetExtend.applicationId = null
  878. // forms.musicSheetExtend.organizationRoleId = null
  879. }}
  880. />
  881. </NFormItemGi>
  882. {forms.sourceType === 'PERSON' && (
  883. <NFormItemGi
  884. label="所属人"
  885. path="musicSheetExtend.userId"
  886. rule={[
  887. {
  888. required: true,
  889. message: '请选择曲目所属人',
  890. trigger: ['input', 'change']
  891. }
  892. ]}
  893. >
  894. <NButton
  895. disabled={state.previewMode || !forms.sourceType}
  896. type="primary"
  897. size="small"
  898. text
  899. //v-auth="orchestraSubsidyStandard/update1597887579789053953"
  900. onClick={() => {
  901. state.showMusicSheetOwnerDialog = true
  902. }}
  903. >
  904. {state.ownerName ? state.ownerName : '请选择所属人'}
  905. </NButton>
  906. </NFormItemGi>
  907. )}
  908. {forms.sourceType === 'ORG' && (
  909. <NFormItemGi
  910. label="所属人"
  911. path="musicSheetExtend.organizationRoleId"
  912. rule={[
  913. {
  914. required: true,
  915. message: '请选择曲目所属机构',
  916. trigger: ['input', 'change']
  917. }
  918. ]}
  919. >
  920. <NButton
  921. disabled={state.previewMode || !forms.sourceType}
  922. type="primary"
  923. size="small"
  924. text
  925. //v-auth="orchestraSubsidyStandard/update1597887579789053953"
  926. onClick={() => {
  927. state.showMusicSheetOwnerDialog = true
  928. }}
  929. >
  930. {state.ownerName ? state.ownerName : '请选择所属机构'}
  931. </NButton>
  932. </NFormItemGi>
  933. )}
  934. </NGrid>
  935. <NGrid cols={2}>
  936. <NFormItemGi
  937. label="审核版本"
  938. path="appAuditFlag"
  939. rule={[
  940. {
  941. required: true,
  942. message: '请选择审核版本',
  943. trigger: 'change',
  944. type: 'number'
  945. }
  946. ]}
  947. >
  948. <NSelect
  949. options={
  950. [
  951. {
  952. label: '是',
  953. value: 1
  954. },
  955. {
  956. label: '否',
  957. value: 0
  958. }
  959. ] as any
  960. }
  961. v-model:value={forms.appAuditFlag}
  962. />
  963. </NFormItemGi>
  964. <NFormItemGi
  965. label="曲目分类"
  966. path="musicCategoryId"
  967. rule={[
  968. {
  969. required: true,
  970. message: '请选择曲目分类',
  971. trigger: ['change']
  972. }
  973. ]}
  974. >
  975. <NCascader
  976. valueField="id"
  977. labelField="name"
  978. children-field="musicSheetCategoriesList"
  979. placeholder="请选择分类"
  980. v-model:value={forms.musicCategoryId}
  981. options={state.musicSheetCategories}
  982. clearable
  983. />
  984. </NFormItemGi>
  985. </NGrid>
  986. <NGrid cols={2}>
  987. <NFormItemGi
  988. label="重复节拍时长"
  989. path="repeatedBeats"
  990. rule={[
  991. {
  992. required: false,
  993. message: '请选择是否重复节拍时长'
  994. }
  995. ]}
  996. >
  997. <NRadioGroup v-model:value={forms.repeatedBeats}>
  998. <NRadio value={true}>是</NRadio>
  999. <NRadio value={false}>否</NRadio>
  1000. </NRadioGroup>
  1001. </NFormItemGi>
  1002. <NFormItemGi
  1003. label="评分标准"
  1004. path="evaluationStandard"
  1005. rule={[
  1006. {
  1007. required: true
  1008. }
  1009. ]}
  1010. >
  1011. <NRadioGroup v-model:value={forms.evaluationStandard}>
  1012. <NRadio value={'FREQUENCY'}>标准评测</NRadio>
  1013. <NRadio value={'AMPLITUDE'}>打击乐(振幅)</NRadio>
  1014. <NRadio value={'DECIBELS'}>节奏(分贝)</NRadio>
  1015. </NRadioGroup>
  1016. </NFormItemGi>
  1017. </NGrid>
  1018. <NGrid cols={2}>
  1019. <NFormItemGi
  1020. label="速度"
  1021. path="playSpeed"
  1022. rule={[
  1023. {
  1024. required: false,
  1025. message: '请输入速度'
  1026. }
  1027. ]}
  1028. >
  1029. <NInputNumber
  1030. placeholder="请输入速度"
  1031. v-model:value={forms.playSpeed}
  1032. style="width:100%"
  1033. />
  1034. </NFormItemGi>
  1035. </NGrid>
  1036. <NAlert showIcon={false} style={{ marginBottom: '12px' }}>
  1037. 曲目上传
  1038. </NAlert>
  1039. <NGrid cols={2}>
  1040. <NFormItemGi
  1041. label="播放模式"
  1042. path="playMode"
  1043. rule={[
  1044. {
  1045. required: true,
  1046. message: '请选择播放模式'
  1047. }
  1048. ]}
  1049. >
  1050. <NRadioGroup
  1051. v-model:value={forms.playMode}
  1052. onUpdateValue={(value: string | number | boolean) => {
  1053. if (value === 'MP3') {
  1054. forms.playMode = 'MP3'
  1055. } else {
  1056. forms.playMode = 'MIDI'
  1057. }
  1058. }}
  1059. >
  1060. <NRadio value="MP3">MP3</NRadio>
  1061. <NRadio value="MIDI">MID</NRadio>
  1062. </NRadioGroup>
  1063. </NFormItemGi>
  1064. {forms.playMode === 'MP3' && (
  1065. <NFormItemGi
  1066. label="伴奏类型"
  1067. path="audioType"
  1068. rule={[
  1069. {
  1070. required: true,
  1071. message: '请选择伴奏类型'
  1072. }
  1073. ]}
  1074. >
  1075. <NRadioGroup v-model:value={forms.audioType}>
  1076. <NRadio value={'HOMEMODE'}>自制伴奏</NRadio>
  1077. <NRadio value={'COMMON'}>普通伴奏</NRadio>
  1078. </NRadioGroup>
  1079. </NFormItemGi>
  1080. )}
  1081. </NGrid>
  1082. <NGrid cols={2}>
  1083. {forms.playMode === 'MP3' && (
  1084. <NFormItemGi
  1085. label="上传伴奏"
  1086. path="musicSheetAccompanimentList"
  1087. rule={[
  1088. {
  1089. required: false,
  1090. message: '请选择上传.mp3'
  1091. }
  1092. ]}
  1093. >
  1094. <UploadFile
  1095. disabled={state.previewMode}
  1096. size={10}
  1097. v-model:imageList={state.musicSheetAccompanimentUrlList}
  1098. tips="仅支持上传.mp3格式文件"
  1099. listType="image"
  1100. accept=".mp3"
  1101. bucketName="cloud-coach"
  1102. text="点击上传伴奏文件"
  1103. max={10}
  1104. desc={'上传伴奏文件'}
  1105. onUpload:success={(file) => {
  1106. state.musicSheetAccompanimentUrls = [
  1107. state.musicSheetAccompanimentUrls,
  1108. file.url
  1109. ]
  1110. .filter(Boolean)
  1111. .join(',')
  1112. state.musicSheetAccompanimentUrlList = state.musicSheetAccompanimentUrls
  1113. ?.split(',')
  1114. .filter(Boolean)
  1115. forms.musicSheetAccompanimentList = []
  1116. for (let i = 0; i < state.musicSheetAccompanimentUrlList.length; i++) {
  1117. forms.musicSheetAccompanimentList.push({
  1118. audioFileUrl: state.musicSheetAccompanimentUrlList[i],
  1119. sortNumber: i + 1
  1120. })
  1121. }
  1122. }}
  1123. onRemove={() => {
  1124. state.musicSheetAccompanimentUrlList = []
  1125. state.musicSheetAccompanimentUrls = ''
  1126. }}
  1127. // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  1128. multiple={true}
  1129. />
  1130. </NFormItemGi>
  1131. )}
  1132. {forms.playMode === 'MIDI' && (
  1133. <NFormItemGi
  1134. label="上传MID"
  1135. path="midiFileUrl"
  1136. rule={[
  1137. {
  1138. required: true,
  1139. message: '请选择上传.MID格式文件'
  1140. }
  1141. ]}
  1142. >
  1143. <UploadFile
  1144. desc={'MIDI文件'}
  1145. disabled={state.previewMode}
  1146. size={10}
  1147. v-model:fileList={forms.midiFileUrl}
  1148. tips="仅支持上传.MID格式文件"
  1149. listType="image"
  1150. accept=".mid"
  1151. bucketName="cloud-coach"
  1152. text="点击上传MID文件"
  1153. // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  1154. />
  1155. </NFormItemGi>
  1156. )}
  1157. <NFormItemGi
  1158. label="上传XML"
  1159. path="xmlFileUrl"
  1160. rule={[
  1161. {
  1162. required: true,
  1163. message: '请选择上传XML',
  1164. trigger: ['change', 'input']
  1165. }
  1166. ]}
  1167. >
  1168. <UploadFile
  1169. desc={'XML文件'}
  1170. disabled={state.previewMode}
  1171. size={10}
  1172. key={'xmlFileUrl'}
  1173. v-model:fileList={forms.xmlFileUrl}
  1174. tips="仅支持上传.xml/.mxml格式文件"
  1175. listType="image"
  1176. accept=".xml,.mxml"
  1177. bucketName="cloud-coach"
  1178. text="点击上传XML文件"
  1179. onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
  1180. onRemove={() => {
  1181. forms.multiTracksSelection = []
  1182. state.partListNames = []
  1183. forms.musicSheetSoundList = []
  1184. forms.musicalInstrumentIdList = []
  1185. forms.subjectIds = []
  1186. }}
  1187. />
  1188. </NFormItemGi>
  1189. </NGrid>
  1190. <NGrid cols={2}>
  1191. <NFormItemGi
  1192. label="可用声部"
  1193. path="subjectIds"
  1194. rule={[
  1195. {
  1196. required: true,
  1197. message: '请选择可用声部',
  1198. trigger: 'change',
  1199. type: 'array'
  1200. }
  1201. ]}
  1202. >
  1203. <NSelect
  1204. v-model:value={forms.subjectIds}
  1205. options={state.subjectList}
  1206. multiple
  1207. filterable
  1208. clearable
  1209. placeholder="请选择可用声部"
  1210. maxTagCount={2}
  1211. />
  1212. </NFormItemGi>
  1213. <NFormItemGi
  1214. label="可用乐器"
  1215. path="musicalInstrumentIdList"
  1216. rule={[
  1217. {
  1218. required: true,
  1219. message: '请选择可用乐器',
  1220. trigger: 'change',
  1221. type: 'array'
  1222. }
  1223. ]}
  1224. >
  1225. <NSelect
  1226. placeholder="请选择可用乐器"
  1227. options={state.instrumentList}
  1228. v-model:value={forms.musicalInstrumentIdList}
  1229. clearable
  1230. multiple
  1231. maxTagCount={2}
  1232. />
  1233. </NFormItemGi>
  1234. </NGrid>
  1235. <NGrid cols={2}>
  1236. <NFormItemGi
  1237. label="多声轨渲染"
  1238. path="musicSheetType"
  1239. rule={[
  1240. {
  1241. required: true,
  1242. message: '请选择多声轨渲染',
  1243. trigger: 'change'
  1244. }
  1245. ]}
  1246. >
  1247. {/*<NSelect*/}
  1248. {/* placeholder="请选择曲目类型"*/}
  1249. {/* v-model:value={forms.musicSheetType}*/}
  1250. {/* options={getSelectDataFromObj(musicSheetType)}*/}
  1251. {/*/>*/}
  1252. <NRadioGroup v-model:value={forms.musicSheetType}>
  1253. <NRadio value={'SINGLE'}>是</NRadio>
  1254. <NRadio value={'CONCERT'}>否</NRadio>
  1255. </NRadioGroup>
  1256. </NFormItemGi>
  1257. </NGrid>
  1258. {forms.musicSheetType && (
  1259. <NGrid cols={1}>
  1260. <NFormItemGi
  1261. label={`${forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'}`}
  1262. path="multiTracksSelection"
  1263. rule={[
  1264. {
  1265. required: true,
  1266. message: `请选择${
  1267. forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'
  1268. }`,
  1269. trigger: 'change',
  1270. type: 'array'
  1271. }
  1272. ]}
  1273. >
  1274. <NGrid style="padding-top: 4px;">
  1275. <NGi span={24}>
  1276. <NRadioGroup
  1277. v-model:value={state.multiTracks}
  1278. onUpdateValue={(value) => {
  1279. checkMultiTracks(value)
  1280. }}
  1281. >
  1282. <NRadio value={'all'}>全选</NRadio>
  1283. <NRadio value={'allUncheck'}>重置</NRadio>
  1284. <NRadio value={'invert'}>反选</NRadio>
  1285. </NRadioGroup>
  1286. </NGi>
  1287. {(state.partListNames && state.partListNames.length > 0) && (
  1288. <NGi span={24} style={'margin-top:5px'}>
  1289. <NFormItemGi
  1290. label=""
  1291. path="multiTracksSelection"
  1292. rule={[
  1293. {
  1294. required: false
  1295. }
  1296. ]}
  1297. >
  1298. <NCheckboxGroup v-model:value={forms.multiTracksSelection}>
  1299. <NGrid yGap={2} cols={4}>
  1300. {state.partListNames.map((item: any) => (
  1301. <NGi>
  1302. <NCheckbox value={item.value} label={item.label} />
  1303. </NGi>
  1304. ))}
  1305. </NGrid>
  1306. </NCheckboxGroup>
  1307. </NFormItemGi>
  1308. </NGi>
  1309. )}
  1310. </NGrid>
  1311. </NFormItemGi>
  1312. </NGrid>
  1313. )}
  1314. <NGrid cols={2}>
  1315. <NFormItemGi
  1316. label="是否播放节拍器"
  1317. path="isPlayBeat"
  1318. rule={[
  1319. {
  1320. required: true,
  1321. message: '请选择是否播放节拍器'
  1322. }
  1323. ]}
  1324. >
  1325. <NRadioGroup v-model:value={forms.isPlayBeat}>
  1326. <NRadio value={true}>是</NRadio>
  1327. <NRadio value={false}>否</NRadio>
  1328. </NRadioGroup>
  1329. </NFormItemGi>
  1330. {forms.isPlayBeat && (
  1331. <NFormItemGi
  1332. label="播放方式"
  1333. path="isUseSystemBeat"
  1334. rule={[
  1335. {
  1336. required: true,
  1337. message: '请选择播放方式'
  1338. }
  1339. ]}
  1340. >
  1341. <NRadioGroup v-model:value={forms.isUseSystemBeat}>
  1342. <NRadio value={true}>系统节拍器</NRadio>
  1343. <NRadio value={false}>MP3节拍器</NRadio>
  1344. </NRadioGroup>
  1345. </NFormItemGi>
  1346. )}
  1347. </NGrid>
  1348. {!!gradualData.list.length && (
  1349. <>
  1350. <NAlert showIcon={false} type="info">
  1351. 识别到共1处渐变速度,请输入Dorico对应小节时间信息
  1352. </NAlert>
  1353. <NFormItem label="rit." required style={{ marginTop: '10px' }}>
  1354. <NSpace vertical>
  1355. {gradualData.list.map((n: any, ni: number) => (
  1356. <NInputGroup>
  1357. <NFormItem
  1358. path={`graduals.${n[0].measureIndex}`}
  1359. rule={[
  1360. { required: true, message: '请输入合奏曲目时间' },
  1361. {
  1362. pattern: /^((\d{2}):?){2,3}$/,
  1363. message: '请输入正确的曲目时间',
  1364. trigger: 'blur'
  1365. }
  1366. ]}
  1367. >
  1368. <NInputGroup>
  1369. <NInputGroupLabel>{n[0].measureIndex}小节开始</NInputGroupLabel>
  1370. <NInput
  1371. placeholder="00:00:00"
  1372. v-model:value={forms.graduals[n[0].measureIndex]}
  1373. ></NInput>
  1374. </NInputGroup>
  1375. </NFormItem>
  1376. <div style={{ lineHeight: '30px', padding: '0 4px' }}>~</div>
  1377. <NFormItem
  1378. path={`graduals.${n[1].measureIndex}`}
  1379. rule={[
  1380. { required: true, message: '请输入合奏曲目时间' },
  1381. {
  1382. pattern: /^((\d{2}):?){2,3}$/,
  1383. message: '请输入正确的曲目时间',
  1384. trigger: 'blur'
  1385. }
  1386. ]}
  1387. >
  1388. <NInputGroup>
  1389. <NInput
  1390. placeholder="00:00:00"
  1391. v-model:value={forms.graduals[n[1].measureIndex]}
  1392. ></NInput>
  1393. <NInputGroupLabel>{n[1].measureIndex}小节结束</NInputGroupLabel>
  1394. </NInputGroup>
  1395. </NFormItem>
  1396. </NInputGroup>
  1397. ))}
  1398. </NSpace>
  1399. </NFormItem>
  1400. </>
  1401. )}
  1402. {/* 只有播放类型为mp3时才会有原音 */}
  1403. {forms.playMode === 'MP3' && forms.musicSheetSoundList.length > 0 && (
  1404. <>
  1405. {forms.musicSheetSoundList.map((item: any, index: number) => {
  1406. return (
  1407. <>
  1408. {(!containOther(item.track) || (item.track?.toLocaleUpperCase?.() != 'COMMON' && forms.multiTracksSelection.includes(item.track)))
  1409. && (
  1410. <NGrid
  1411. class={styles.audioSection}
  1412. // v-show={forms.multiTracksSelection.indexOf(item.track) > -1}
  1413. >
  1414. <NFormItemGi
  1415. span={12}
  1416. label="原音"
  1417. path={`musicSheetSoundList[${index}].audioFileUrl`}
  1418. rule={[
  1419. {
  1420. // required: forms.multiTracksSelection.indexOf(forms.musicSheetSoundList[index].audioFileUrl) > -1,
  1421. required: true,
  1422. message: `请上传${
  1423. item.track ? item.track + '的' : '第' + (index + 1) + '个'
  1424. }原音`
  1425. }
  1426. ]}
  1427. >
  1428. <UploadFile
  1429. desc={'原音文件'}
  1430. disabled={state.previewMode}
  1431. size={100}
  1432. v-model:fileList={item.audioFileUrl}
  1433. tips="仅支持上传.mp3格式文件"
  1434. listType="image"
  1435. accept=".mp3"
  1436. bucketName="cloud-coach"
  1437. />
  1438. </NFormItemGi>
  1439. {state.partListNames.length > 0 && (
  1440. <NFormItemGi
  1441. span={12}
  1442. label="所属轨道"
  1443. path={`musicSheetSoundList[${index}].track`}
  1444. rule={[
  1445. {
  1446. required: true,
  1447. message: '请选择所属轨道'
  1448. }
  1449. ]}
  1450. >
  1451. <NSelect
  1452. placeholder="请选择所属轨道"
  1453. value={item.track}
  1454. options={initPartsListStatus(item.track)}
  1455. onUpdateValue={(value: any) => {
  1456. const track = item.track
  1457. if (track) {
  1458. // 声轨交换
  1459. forms.musicSheetSoundList.forEach((next: any) => {
  1460. if (next.track == value) {
  1461. next.track = track
  1462. }
  1463. })
  1464. const index = forms.multiTracksSelection.indexOf(item.track)
  1465. forms.multiTracksSelection.splice(index, 1)
  1466. } else {
  1467. forms.musicSheetSoundList = forms.musicSheetSoundList.filter((next:any) => {
  1468. return next.track != value
  1469. })
  1470. }
  1471. if (value && !forms.multiTracksSelection.includes(value)) {
  1472. forms.multiTracksSelection.push(value)
  1473. }
  1474. item.track = value
  1475. }}
  1476. />
  1477. </NFormItemGi>
  1478. )}
  1479. <NGi class={styles.btnRemove}>
  1480. <NButton
  1481. type="primary"
  1482. text
  1483. // disabled={forms.musicSheetSoundList.length === 1}
  1484. onClick={() => removeSys(index)}
  1485. >
  1486. 删除
  1487. </NButton>
  1488. </NGi>
  1489. </NGrid>
  1490. )}
  1491. </>
  1492. );
  1493. })}
  1494. </>
  1495. )}
  1496. </NForm>
  1497. </NSpin>
  1498. {props.type !== 'preview' && (
  1499. <NSpace justify="end" style="padding-top:12px">
  1500. <NButton type="default" onClick={() => emit('close')}>
  1501. 取消
  1502. </NButton>
  1503. <NButton
  1504. type="primary"
  1505. onClick={() => onSubmit()}
  1506. loading={btnLoading.value}
  1507. disabled={btnLoading.value}
  1508. >
  1509. 确认
  1510. </NButton>
  1511. </NSpace>
  1512. )}
  1513. <NModal
  1514. v-model:show={state.showMusicSheetOwnerDialog}
  1515. preset="dialog"
  1516. showIcon={false}
  1517. maskClosable={false}
  1518. title="所属人"
  1519. style={{ width: '800px' }}
  1520. >
  1521. <MusicSheetOwnerDialog
  1522. musicSheetExtend={forms.musicSheetExtend}
  1523. sourceType={forms.sourceType}
  1524. appData={state.appData}
  1525. onClose={() => {
  1526. state.showMusicSheetOwnerDialog = false
  1527. }}
  1528. onChoseMusicSheetOwnerData={(musicSheetOwnerData) => {
  1529. forms.musicSheetExtend = {
  1530. ...musicSheetOwnerData
  1531. }
  1532. setOwnerName()
  1533. }}
  1534. />
  1535. </NModal>
  1536. <NModal
  1537. class={styles.productModal}
  1538. title="自动生成曲谱图片"
  1539. v-model:show={state.productOpen}
  1540. preset="dialog"
  1541. closeOnEsc={false}
  1542. maskClosable={false}
  1543. showIcon={false}
  1544. >
  1545. <MusicCreateImg
  1546. xmlFileUrl={forms.xmlFileUrl || ''}
  1547. onClose={() => (state.productOpen = false)}
  1548. onConfirm={async (item: any) => {
  1549. // 保存
  1550. try {
  1551. forms.musicImg = item.musicImg
  1552. forms.musicFirstImg = item.musicFirstImg
  1553. forms.musicJianImg = item.musicJianImg
  1554. onSubmit()
  1555. } catch (e: any) {
  1556. //
  1557. console.log(e, 'e')
  1558. }
  1559. setTimeout(() => {
  1560. state.isAutoSave = false
  1561. }, 50)
  1562. }}
  1563. />
  1564. </NModal>
  1565. </div>
  1566. )
  1567. }
  1568. })