|
@@ -0,0 +1,2090 @@
|
|
|
+import type {SelectOption} from 'naive-ui'
|
|
|
+import {
|
|
|
+ NAlert,
|
|
|
+ NButton,
|
|
|
+ NCascader,
|
|
|
+ NCheckbox,
|
|
|
+ NCheckboxGroup,
|
|
|
+ NForm,
|
|
|
+ NFormItem,
|
|
|
+ NFormItemGi,
|
|
|
+ NGi,
|
|
|
+ NGrid,
|
|
|
+ NInput,
|
|
|
+ NInputGroup,
|
|
|
+ NInputGroupLabel,
|
|
|
+ NInputNumber,
|
|
|
+ NModal,
|
|
|
+ NRadio,
|
|
|
+ NRadioGroup,
|
|
|
+ NSelect,
|
|
|
+ NSpace,
|
|
|
+ NSpin,
|
|
|
+ useDialog,
|
|
|
+ useMessage
|
|
|
+} from 'naive-ui'
|
|
|
+import {defineComponent, onMounted, PropType, reactive, ref} from 'vue'
|
|
|
+import {musicSheetCategoriesQueryTree, musicSheetDetail, musicSheetSave} from '../../api'
|
|
|
+import UploadFile from '@/components/upload-file'
|
|
|
+import styles from './index.module.less'
|
|
|
+import deepClone from '@/utils/deep.clone'
|
|
|
+import axios from 'axios'
|
|
|
+import {appKey, clientType, musicSheetSourceType} from '@/utils/constant'
|
|
|
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
|
|
|
+import {musicalInstrumentPage} from '@views/system-manage/subject-manage/api'
|
|
|
+import {subjectPage} from '@views/system-manage/api'
|
|
|
+import MusicSheetOwnerDialog from '@views/music-library/music-sheet/modal/musicSheetOwnerDialog'
|
|
|
+import {sysApplicationPage} from '@views/menu-manage/api'
|
|
|
+import {filterPointCategory} from '@views/teaching-manage/unit-test'
|
|
|
+import MusicCreateImg from './music-create-img'
|
|
|
+import {TABS_ROUTES} from "@/store/mutation-types";
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取指定元素下一个Note元素
|
|
|
+ * @param ele 指定元素
|
|
|
+ * @param selectors 选择器
|
|
|
+ */
|
|
|
+const getNextNote = (ele: any, selectors: any) => {
|
|
|
+ let index = 0
|
|
|
+ const parentEle = ele.closest(selectors)
|
|
|
+ let pointer = parentEle
|
|
|
+ const measure = parentEle?.closest('measure')
|
|
|
+ let siblingNote = null
|
|
|
+ // 查找到相邻的第一个note元素
|
|
|
+ while (!siblingNote && index < (measure?.childNodes.length || 50)) {
|
|
|
+ index++
|
|
|
+ if (pointer?.nextElementSibling?.tagName === 'note') {
|
|
|
+ siblingNote = pointer?.nextElementSibling
|
|
|
+ }
|
|
|
+ pointer = pointer?.nextElementSibling
|
|
|
+ }
|
|
|
+ return siblingNote
|
|
|
+}
|
|
|
+
|
|
|
+export const onlyVisible = (xml: any, partIndex: any) => {
|
|
|
+ if (!xml) return ''
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
|
|
|
+ const partList =
|
|
|
+ xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
|
|
|
+ const parts = xmlParse.getElementsByTagName('part')
|
|
|
+ const visiblePartInfo = partList[partIndex]
|
|
|
+ if (visiblePartInfo) {
|
|
|
+ const id = visiblePartInfo.getAttribute('id')
|
|
|
+ Array.from(parts).forEach((part) => {
|
|
|
+ if (part && part.getAttribute('id') !== id) {
|
|
|
+ part.parentNode?.removeChild(part)
|
|
|
+ // 不等于第一行才添加避免重复添加
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最后一个小节的结束线元素不在最后 调整
|
|
|
+ if (part && part.getAttribute('id') === id) {
|
|
|
+ const barlines = part.getElementsByTagName('barline')
|
|
|
+ const lastParent = barlines[barlines.length - 1]?.parentElement
|
|
|
+ if (lastParent?.lastElementChild?.tagName !== 'barline') {
|
|
|
+ const children: any[] = (lastParent?.children as any) || []
|
|
|
+ for (let el of children) {
|
|
|
+ if (el.tagName === 'barline') {
|
|
|
+ // 将结束线元素放到最后
|
|
|
+ lastParent?.appendChild(el)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ Array.from(partList).forEach((part) => {
|
|
|
+ if (part && part.getAttribute('id') !== id) {
|
|
|
+ part.parentNode?.removeChild(part)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 处理装饰音问题
|
|
|
+ const notes = xmlParse.getElementsByTagName('note')
|
|
|
+ const getNextvNoteDuration = (i: any) => {
|
|
|
+ let nextNote = notes[i + 1]
|
|
|
+ // 可能存在多个装饰音问题,取下一个非装饰音时值
|
|
|
+ for (let index = i; index < notes.length; index++) {
|
|
|
+ const note = notes[index]
|
|
|
+ if (!note.getElementsByTagName('grace')?.length) {
|
|
|
+ nextNote = note
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nextNote?.getElementsByTagName('duration')[0]
|
|
|
+ }
|
|
|
+ Array.from(notes).forEach((note, i) => {
|
|
|
+ const graces = note.getElementsByTagName('grace')
|
|
|
+ if (graces && graces.length) {
|
|
|
+ note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return new XMLSerializer().serializeToString(xmlParse)
|
|
|
+}
|
|
|
+
|
|
|
+const speedInfo = {
|
|
|
+ 'rall.': 1.333333333,
|
|
|
+ 'poco rit.': 1.333333333,
|
|
|
+ 'rit.': 1.333333333,
|
|
|
+ 'molto rit.': 1.333333333,
|
|
|
+ 'molto rall': 1.333333333,
|
|
|
+ molto: 1.333333333,
|
|
|
+ lentando: 1.333333333,
|
|
|
+ allargando: 1.333333333,
|
|
|
+ morendo: 1.333333333,
|
|
|
+ 'accel.': 0.8,
|
|
|
+ calando: 2,
|
|
|
+ 'poco accel.': 0.8,
|
|
|
+ 'gradually slowing': 1.333333333,
|
|
|
+ slowing: 1.333333333,
|
|
|
+ slow: 1.333333333,
|
|
|
+ slowly: 1.333333333,
|
|
|
+ faster: 1.333333333
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 按照xml进行减慢速度的计算
|
|
|
+ * @param xml 始终按照第一分谱进行减慢速度的计算
|
|
|
+ */
|
|
|
+export function getGradualLengthByXml(xml: string) {
|
|
|
+ const firstPartXml = onlyVisible(xml, 0)
|
|
|
+ const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
|
|
|
+ const measures = Array.from(xmlParse.querySelectorAll('measure'))
|
|
|
+ const notes = Array.from(xmlParse.querySelectorAll('note'))
|
|
|
+ const words = Array.from(xmlParse.querySelectorAll('words'))
|
|
|
+ const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
|
|
|
+
|
|
|
+ const eles = []
|
|
|
+
|
|
|
+ for (const ele of [...words, ...metronomes]) {
|
|
|
+ const note = getNextNote(ele, 'direction')
|
|
|
+ // console.log(ele, note)
|
|
|
+ if (note) {
|
|
|
+ const measure = note?.closest('measure')
|
|
|
+ const measureNotes = Array.from(measure.querySelectorAll('note'))
|
|
|
+
|
|
|
+ const noteInMeasureIndex = Array.from(measure.childNodes)
|
|
|
+ .filter((item: any) => item.nodeName === 'note')
|
|
|
+ .findIndex((item) => item === note)
|
|
|
+
|
|
|
+ let allDuration = 0
|
|
|
+ let leftDuration = 0
|
|
|
+ for (let i = 0; i < measureNotes.length; i++) {
|
|
|
+ const n: any = measureNotes[i]
|
|
|
+ const duration = +(n.querySelector('duration')?.textContent || '0')
|
|
|
+ allDuration += duration
|
|
|
+ if (i < noteInMeasureIndex) {
|
|
|
+ leftDuration = allDuration
|
|
|
+ }
|
|
|
+ }
|
|
|
+ eles.push({
|
|
|
+ ele,
|
|
|
+ index: notes.indexOf(note),
|
|
|
+ noteInMeasureIndex,
|
|
|
+ textContent: ele.textContent,
|
|
|
+ measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number')
|
|
|
+ type: ele.tagName,
|
|
|
+ allDuration,
|
|
|
+ leftDuration
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 结尾处手动插入一个音符节点
|
|
|
+ eles.push({
|
|
|
+ ele: notes[notes.length - 1],
|
|
|
+ index: notes.length,
|
|
|
+ noteInMeasureIndex: 0,
|
|
|
+ textContent: '',
|
|
|
+ type: 'metronome',
|
|
|
+ allDuration: 1,
|
|
|
+ leftDuration: 1,
|
|
|
+ measureIndex: measures.length
|
|
|
+ })
|
|
|
+
|
|
|
+ const gradualNotes: any[] = []
|
|
|
+ eles.sort((a, b) => a.index - b.index)
|
|
|
+ const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase())
|
|
|
+ let isLastNoteAndNotClosed = false
|
|
|
+ for (const ele of eles) {
|
|
|
+ const textContent: any = ele.textContent?.toLocaleLowerCase().trim()
|
|
|
+ if (ele === eles[eles.length - 1]) {
|
|
|
+ if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
|
|
|
+ isLastNoteAndNotClosed = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const isKeyWork = keys.find((k) => {
|
|
|
+ const ks = k.split(' ')
|
|
|
+ return textContent && ks.includes(textContent)
|
|
|
+ })
|
|
|
+ if (
|
|
|
+ ele.type === 'metronome' ||
|
|
|
+ (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) ||
|
|
|
+ isLastNoteAndNotClosed
|
|
|
+ ) {
|
|
|
+ const indexOf = gradualNotes.findIndex((item) => item.length === 1)
|
|
|
+ if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
|
|
|
+ gradualNotes[indexOf][1] = {
|
|
|
+ start: ele.index,
|
|
|
+ measureIndex: ele.measureIndex,
|
|
|
+ noteInMeasureIndex: ele.noteInMeasureIndex,
|
|
|
+ allDuration: ele.allDuration,
|
|
|
+ leftDuration: ele.leftDuration,
|
|
|
+ type: textContent
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ele.type === 'words' && isKeyWork) {
|
|
|
+ gradualNotes.push([
|
|
|
+ {
|
|
|
+ start: ele.index,
|
|
|
+ measureIndex: ele.measureIndex,
|
|
|
+ noteInMeasureIndex: ele.noteInMeasureIndex,
|
|
|
+ allDuration: ele.allDuration,
|
|
|
+ leftDuration: ele.leftDuration,
|
|
|
+ type: textContent
|
|
|
+ }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return gradualNotes
|
|
|
+}
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: 'music-operation',
|
|
|
+ props: {
|
|
|
+ type: {
|
|
|
+ type: String,
|
|
|
+ default: 'add'
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ type: Object as PropType<any>,
|
|
|
+ default: () => {
|
|
|
+ }
|
|
|
+ },
|
|
|
+ tagList: {
|
|
|
+ type: Array as PropType<Array<SelectOption>>,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ subjectList: {
|
|
|
+ type: Array as PropType<Array<SelectOption>>,
|
|
|
+ default: () => []
|
|
|
+ }
|
|
|
+ // musicSheetCategories: {
|
|
|
+ // type: Array as PropType<Array<SelectOption>>,
|
|
|
+ // default: () => []
|
|
|
+ // }
|
|
|
+ },
|
|
|
+ emits: ['close', 'getList'],
|
|
|
+
|
|
|
+ setup(props, {slots, attrs, emit}) {
|
|
|
+ const forms = reactive({
|
|
|
+ details: {} as any, // 曲目详情
|
|
|
+ graduals: {} as any, // 渐变速度
|
|
|
+ playMode: 'MP3', // 播放类型
|
|
|
+ xmlFileUrl: null, // XML
|
|
|
+ midiUrl: null, // mid
|
|
|
+ name: null, // 曲目名称
|
|
|
+ // musicTag: [] as any, // 曲目标签
|
|
|
+ composer: null, // 音乐人
|
|
|
+ playSpeed: null as any, // 曲谱速度
|
|
|
+ // showFingering: null as any, // 是否显示指法
|
|
|
+ // canEvaluate: null as any, // 是否评测
|
|
|
+ // notation: null as any, // 能否转和简谱
|
|
|
+ // auditVersion: null as any, // 审核版本
|
|
|
+ // sortNumber: null, // 排序
|
|
|
+ musicCover: null, // 曲谱封面
|
|
|
+ remark: null, // 曲谱描述
|
|
|
+ // musicSheetSoundList: [] as any, // 原音
|
|
|
+ musicSheetSoundList_YY: [] as any, // 演唱原音
|
|
|
+ musicSheetSoundList_YZ: [] as any, // 演奏演奏
|
|
|
+ musicSheetSoundList_all_subject: null, // 全部声部原音
|
|
|
+ // musicSheetCategoriesId: null,
|
|
|
+ status: false,
|
|
|
+ musicSheetType: 'SINGLE', // 曲目类型
|
|
|
+ sourceType: null as any, //来源类型/作者属性(PLATFORM: 平台; ORG: 机构; PERSON: 个人)
|
|
|
+ // userId: null, // 所属人
|
|
|
+ appAuditFlag: 0, // 是否审核版本
|
|
|
+ midiFileUrl: null, // 伴奏文件 MIDI文件(保留字段)
|
|
|
+ subjectIds: [] as any, // 可用声部
|
|
|
+ musicalInstrumentIdList: [] as any, //可用乐器
|
|
|
+ musicCategoryId: null, //曲目分类
|
|
|
+ musicSheetAccompanimentList: [] as any, //曲目伴奏
|
|
|
+ audioType: 'HOMEMODE', // 伴奏类型
|
|
|
+ isPlayBeat: true, // 是否播放节拍器
|
|
|
+ isUseSystemBeat: true, // 是否使用系统节拍器(0:否;1:是)
|
|
|
+ isUseSingSystemBeat: true, //演唱是否使用系统节拍器(0:否;1:是)
|
|
|
+ repeatedBeats: false, // 是否重复节拍时长
|
|
|
+ repeatedBeatsToSing: false, //演唱是否重复节拍时长
|
|
|
+ isPlaySingBeat: true, //是否播入演唱节拍器(0: 否 1:是)
|
|
|
+ evaluationStandard: 'FREQUENCY', // 评分标准 节奏 AMPLITUDE 音准 FREQUENCY 分贝 DECIBELS
|
|
|
+ multiTracksSelection: [] as any, // 声轨
|
|
|
+ musicSheetExtend: {} as any, //所属人信息
|
|
|
+ musicImg: '', // 五线谱图片
|
|
|
+ musicFirstImg: '', //首调图片
|
|
|
+ musicJianImg: '', // 简谱固定调
|
|
|
+ isEvxml: false, // 是否是evxml
|
|
|
+ isShowFingering: true, // 是否显示指法
|
|
|
+ solmizationFileUrl: null, // 唱名文件
|
|
|
+ isAllSubject: false, // 适用声部
|
|
|
+ })
|
|
|
+ const state = reactive({
|
|
|
+ loading: false,
|
|
|
+ previewMode: false, //是否是预览模式
|
|
|
+ tagList: [...props.tagList] as any, // 标签列表
|
|
|
+ xmlFirstSpeed: null as any, // 第一个音轨速度
|
|
|
+ partListNames: [] as any, // 所有音轨声部列表
|
|
|
+ musicSheetCategories: [] as any,
|
|
|
+ musicSheetAccompanimentUrls: '' as any,
|
|
|
+ musicSheetAccompanimentUrlList: [] as any,
|
|
|
+ instrumentData: [],
|
|
|
+ instrumentList: [] as any,
|
|
|
+ instrumentIdNameMap: new Map() as any,
|
|
|
+ subjectList: [] as any,
|
|
|
+ showMusicSheetOwnerDialog: false, //所属人弹框
|
|
|
+ // musicSheetOwnerData: {}, //所属人信息
|
|
|
+ multiTracks: null,
|
|
|
+ multiInstruments: null,
|
|
|
+ appData: [], // 应用列表
|
|
|
+ ownerName: null as any, // 所属人名称描述
|
|
|
+
|
|
|
+ productOpen: false, // 是否打开自动生成图片
|
|
|
+ productItem: {} as any,
|
|
|
+ productIfameSrc: '',
|
|
|
+ isAutoSave: false, // 是否自动保存
|
|
|
+ fSongFile: null as any, // 范唱
|
|
|
+ bSongFile: null as any, // 伴唱
|
|
|
+ musicSheetSoundList: [] as any,
|
|
|
+ })
|
|
|
+ const gradualData = reactive({
|
|
|
+ list: [] as any[],
|
|
|
+ gradualRefs: [] as any[]
|
|
|
+ })
|
|
|
+
|
|
|
+ const btnLoading = ref(false)
|
|
|
+ const formsRef = ref()
|
|
|
+ const message = useMessage()
|
|
|
+ const dialog = useDialog()
|
|
|
+
|
|
|
+ // 提交记录
|
|
|
+ const onSubmit = async () => {
|
|
|
+ formsRef.value.validate(async (error: any) => {
|
|
|
+ if (error) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 校验合奏时声轨与乐器是否存在不匹配情况
|
|
|
+ if (forms.musicSheetType == 'CONCERT') {
|
|
|
+ let set = [] as any;
|
|
|
+ const {data} = await musicalInstrumentPage({page: 1, rows: 9999})
|
|
|
+ data.rows.map((row: any) => {
|
|
|
+ if (row.code) {
|
|
|
+ row.code.split(',').forEach((code: string) => {
|
|
|
+ let temp = code.replaceAll(' ', '')
|
|
|
+ set.push(temp.toLocaleLowerCase())
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ let unDefinedTrack = [] as any
|
|
|
+ forms.multiTracksSelection.forEach((item: any) => {
|
|
|
+ if (item) {
|
|
|
+ let contain = false;
|
|
|
+ let code = item.replaceAll(' ', '').toLocaleLowerCase()
|
|
|
+ for (let i = 0; i < set.length; i++) {
|
|
|
+ if (set[i].startsWith(code) || set[i].endsWith(code)) {
|
|
|
+ contain = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!contain) {
|
|
|
+ unDefinedTrack.push(item)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (unDefinedTrack.length > 0) {
|
|
|
+ dialog.warning({
|
|
|
+ title: '提示',
|
|
|
+ content: `声轨未配置:${unDefinedTrack.join(',')}`,
|
|
|
+ positiveText: '确定',
|
|
|
+ onPositiveClick: () => {
|
|
|
+ },
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!state.isAutoSave) {
|
|
|
+ state.isAutoSave = true
|
|
|
+ state.productOpen = true
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ //extConfigJson: {"repeatedBeats":0,"gradualTimes":{"75":"02:38:60","77":"02:43:39"}}
|
|
|
+ let audioPlayTypes = [] as any;
|
|
|
+ let musicSheetSoundList = [];
|
|
|
+ let musicSheetType = forms.musicSheetType;
|
|
|
+ if (state.fSongFile) {
|
|
|
+ musicSheetSoundList.push({
|
|
|
+ musicSheetId: props.data.id,
|
|
|
+ audioFileUrl: state.fSongFile,
|
|
|
+ audioPlayType: 'SING',
|
|
|
+ solmizationFileUrl: forms.solmizationFileUrl
|
|
|
+ })
|
|
|
+ audioPlayTypes.push("SING")
|
|
|
+ }
|
|
|
+
|
|
|
+ if (forms.isAllSubject) {
|
|
|
+ if (musicSheetType == 'SINGLE' && forms.musicSheetSoundList_all_subject) {
|
|
|
+ audioPlayTypes.push("PLAY")
|
|
|
+ musicSheetSoundList.push({
|
|
|
+ audioFileUrl: forms.musicSheetSoundList_all_subject,
|
|
|
+ musicSheetId: props.data.id,
|
|
|
+ audioPlayType: 'PLAY'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (musicSheetType == 'SINGLE' && forms.musicSheetSoundList_YZ.length > 0) {
|
|
|
+ audioPlayTypes.push("PLAY")
|
|
|
+ forms.musicSheetSoundList_YZ.forEach((musicSheetSound: any) => {
|
|
|
+ if (forms.musicalInstrumentIdList.includes(musicSheetSound.musicalInstrumentId)) {
|
|
|
+ musicSheetSoundList.push({
|
|
|
+ ...musicSheetSound,
|
|
|
+ musicSheetId: props.data.id,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (musicSheetType == 'CONCERT' && forms.musicSheetSoundList_YY.length > 0) {
|
|
|
+ audioPlayTypes.push("PLAY")
|
|
|
+ forms.musicSheetSoundList_YY.forEach((musicSheetSound: any) => {
|
|
|
+ if (musicSheetSound.audioFileUrl) {
|
|
|
+ musicSheetSoundList.push({
|
|
|
+ ...musicSheetSound,
|
|
|
+ musicSheetId: props.data.id,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state.bSongFile) {
|
|
|
+ forms.musicSheetAccompanimentList.push({
|
|
|
+ musicSheetId: props.data.id,
|
|
|
+ audioFileUrl: state.bSongFile,
|
|
|
+ audioPlayType: 'SING'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+
|
|
|
+ const obj = {
|
|
|
+ musicCategoryId: forms.musicCategoryId,
|
|
|
+ musicCover: forms.musicCover,
|
|
|
+ name: forms.name,
|
|
|
+ appAuditFlag: forms.appAuditFlag,
|
|
|
+ subjectIds: forms.isAllSubject ? "" : forms.subjectIds.join(','),
|
|
|
+ remark: forms.remark,
|
|
|
+ audioPlayTypes: audioPlayTypes.join(','),
|
|
|
+ musicalInstrumentIds: forms.isAllSubject ? "" : forms.musicalInstrumentIdList.join(','),
|
|
|
+ composer: forms.composer,
|
|
|
+ musicSheetType: forms.musicSheetType,
|
|
|
+ isUseSystemBeat: forms.isUseSystemBeat,
|
|
|
+ isShowFingering: forms.isShowFingering,
|
|
|
+ isPlayBeat: forms.isPlayBeat,
|
|
|
+ multiTracksSelection: forms.isAllSubject ? "" : forms.multiTracksSelection.join(','),
|
|
|
+ playSpeed: forms.playSpeed,
|
|
|
+ playMode: forms.playMode,
|
|
|
+ xmlFileUrl: forms.xmlFileUrl,
|
|
|
+ musicImg: forms.musicImg,
|
|
|
+ musicFirstImg: forms.musicFirstImg,
|
|
|
+ musicJianImg: forms.musicJianImg,
|
|
|
+ extConfigJson: JSON.stringify({
|
|
|
+ repeatedBeats: forms.repeatedBeats ? 1 : 0,
|
|
|
+ gradualTimes: forms.graduals,
|
|
|
+ isEvxml: forms.isEvxml ? 1 : 0,
|
|
|
+ repeatedBeatsToSing: forms.repeatedBeatsToSing ? 1 : 0
|
|
|
+ }),
|
|
|
+ // availableType: forms.availableType,
|
|
|
+ sourceType: forms.sourceType,
|
|
|
+ audioType: forms.audioType,
|
|
|
+ status: forms.status,
|
|
|
+ evaluationStandard: forms.evaluationStandard,
|
|
|
+ musicSheetAccompanimentList: forms.musicSheetAccompanimentList,
|
|
|
+ musicSheetSoundList: musicSheetSoundList,
|
|
|
+ musicTag: '-1',
|
|
|
+ isUseSingSystemBeat: forms.isUseSingSystemBeat,
|
|
|
+ isPlaySingBeat: forms.isPlaySingBeat,
|
|
|
+ isAllSubject: forms.isAllSubject,
|
|
|
+ }
|
|
|
+ if (forms.audioType == 'MIDI') {
|
|
|
+ obj.musicSheetSoundList = []
|
|
|
+ }
|
|
|
+ btnLoading.value = true
|
|
|
+ if (props.type === 'add') {
|
|
|
+ await musicSheetSave(obj)
|
|
|
+ message.success('添加成功')
|
|
|
+ } else if (props.type === 'edit') {
|
|
|
+ await musicSheetSave({...obj, id: props.data.id})
|
|
|
+ message.success('修改成功')
|
|
|
+ }
|
|
|
+ emit('getList')
|
|
|
+ emit('close')
|
|
|
+ } catch (e) {
|
|
|
+ console.log(e)
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ btnLoading.value = false
|
|
|
+ }, 100)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上传XML,初始化音轨 音轨速度 乐器、声部
|
|
|
+ const readFileInputEventAsArrayBuffer = (file: any) => {
|
|
|
+ // 是否是evxml
|
|
|
+ forms.isEvxml = file?.name?.includes('.evxml') ? true : false;
|
|
|
+ const xmlRead = new FileReader()
|
|
|
+ xmlRead.onload = (res) => {
|
|
|
+ try {
|
|
|
+ gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter(
|
|
|
+ (item: any) => item.length === 2
|
|
|
+ )
|
|
|
+ } catch (error) {
|
|
|
+ }
|
|
|
+ state.partListNames = getPartListNames(res?.target?.result as any) as any
|
|
|
+ // parseInstrumentAndSubject(res?.target?.result as any)
|
|
|
+ // 这里是如果没有当前音轨就重新写
|
|
|
+ for (let j = 0; j < state.partListNames.length; j++) {
|
|
|
+ if (!forms.musicSheetSoundList_YY[j]) {
|
|
|
+ forms.musicSheetSoundList_YY.push({audioFileUrl: null, track: null})
|
|
|
+ }
|
|
|
+ forms.musicSheetSoundList_YY[j].track = state.partListNames[j].value
|
|
|
+ }
|
|
|
+
|
|
|
+ // 循环添加所在音轨的原音
|
|
|
+ for (
|
|
|
+ let index = forms.musicSheetSoundList_YY.length;
|
|
|
+ index < state.partListNames.length;
|
|
|
+ index++
|
|
|
+ ) {
|
|
|
+ const part = state.partListNames[index].value
|
|
|
+ const sysData = {
|
|
|
+ ...forms.musicSheetSoundList_YY[0],
|
|
|
+ track: part
|
|
|
+ }
|
|
|
+ if (!sysData.speed) {
|
|
|
+ sysData.speed = state.xmlFirstSpeed
|
|
|
+ }
|
|
|
+ createSys(sysData)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (forms.musicSheetSoundList_YY.length == 0) {
|
|
|
+ forms.musicSheetSoundList_YY.push({audioFileUrl: '', track: ''})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ xmlRead.readAsText(file)
|
|
|
+ }
|
|
|
+
|
|
|
+ const containOther = (track: any) => {
|
|
|
+ for (let i = 0; i < state.partListNames.length; i++) {
|
|
|
+ if (state.partListNames[i].value == track) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ const parseInstrumentAndSubject = (xml: any) => {
|
|
|
+ if (!xml) return
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
|
|
|
+ // 乐器
|
|
|
+ const instrumentCodeList: any = []
|
|
|
+ const instrumentEle = xmlParse.getElementsByTagName('virtual-instrument')
|
|
|
+ for (let index = 0; index < instrumentEle.length; index++) {
|
|
|
+ const note = instrumentEle[index]
|
|
|
+ const instrumentCode = note.getElementsByTagName('virtual-name')?.[0]?.textContent || ''
|
|
|
+ if (instrumentCode && !instrumentCodeList.includes(instrumentCode)) {
|
|
|
+ instrumentCodeList.push(instrumentCode)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 乐器支持多编码,暂不开放
|
|
|
+ // const codeIdMap = new Map<string, []>() as any
|
|
|
+ // state.instrumentData.forEach((data: any) => {
|
|
|
+ // const codes = data.code.split(/,|,/)
|
|
|
+ // codes.forEach((code: string) => {
|
|
|
+ // if (codeIdMap.has(code)) {
|
|
|
+ // codeIdMap.get(code).push(data.id + '')
|
|
|
+ // } else {
|
|
|
+ // const arr = [] as any;
|
|
|
+ // arr.push(data.id + '')
|
|
|
+ // codeIdMap.set(code, arr)
|
|
|
+ // }
|
|
|
+ // // codeIdMap.set(code, data.id + '')
|
|
|
+ // })
|
|
|
+ // })
|
|
|
+ // forms.musicalInstrumentIdList = []
|
|
|
+ // instrumentCodeList.forEach((code: string) => {
|
|
|
+ // if (codeIdMap.has(code)) {
|
|
|
+ // codeIdMap.get(code).forEach((c: any) => {
|
|
|
+ // forms.musicalInstrumentIdList.push(c)
|
|
|
+ // })
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ const codeIdMap = new Map<string, string>()
|
|
|
+ state.instrumentData.forEach((data: any) => {
|
|
|
+ codeIdMap.set(data.code, data.id + '')
|
|
|
+ })
|
|
|
+ forms.musicalInstrumentIdList = []
|
|
|
+ instrumentCodeList.forEach((code: string) => {
|
|
|
+ if (codeIdMap.has(code)) {
|
|
|
+ forms.musicalInstrumentIdList.push(codeIdMap.get(code))
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 声部
|
|
|
+ if (forms.musicalInstrumentIdList.length > 0) {
|
|
|
+ showBackSubject(forms.musicalInstrumentIdList)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取xml中所有轨道 乐器
|
|
|
+ const getPartListNames = (xml: any) => {
|
|
|
+ if (!xml) return []
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
|
|
|
+ const partList: any =
|
|
|
+ xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
|
|
|
+ let partListNames = Array.from(partList).map((item: any) => {
|
|
|
+ let part = item.getElementsByTagName('part-name')?.[0]?.textContent || ''
|
|
|
+ // evxml没有分轨,需要手动设置一个默认的名称,用于上传原音
|
|
|
+ if (forms.isEvxml) {
|
|
|
+ part = part || 'noPartName'
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ value: part.trim(),
|
|
|
+ label: part.trim()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 处理空数据
|
|
|
+ // if (partListNames.length === 1 && forms.details.id && !partListNames[0].value) {
|
|
|
+ // partListNames[0] = {
|
|
|
+ // value: forms.details.multiTracksSelection,
|
|
|
+ // label: forms.details.multiTracksSelection
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ partListNames = partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
|
|
|
+
|
|
|
+ if (partListNames.length > 0) {
|
|
|
+ forms.musicSheetSoundList_YY = forms.musicSheetSoundList_YY.slice(0, partListNames.length)
|
|
|
+ }
|
|
|
+
|
|
|
+ state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
|
|
|
+ if (!forms.playSpeed) {
|
|
|
+ if (state.xmlFirstSpeed) {
|
|
|
+ forms.playSpeed = Number.parseFloat(state.xmlFirstSpeed)
|
|
|
+ } else {
|
|
|
+ // 速度默认给100
|
|
|
+ forms.playSpeed = 100
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // console.log('xml声轨',partListNames,state.xmlFirstSpeed)
|
|
|
+ return partListNames
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断选择的音轨是否在选中
|
|
|
+ const initPartsListStatus = (track: string): any => {
|
|
|
+ // const _names = state.partListNames.filter(
|
|
|
+ // (n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON'
|
|
|
+ // )
|
|
|
+ const partListNames = deepClone(state.partListNames) || []
|
|
|
+ const multiTracksSelection = forms.multiTracksSelection
|
|
|
+ partListNames.forEach((item: any) => {
|
|
|
+ if (multiTracksSelection.includes(item.value)) {
|
|
|
+ item.disabled = true
|
|
|
+ } else {
|
|
|
+ item.disabled = false
|
|
|
+ }
|
|
|
+
|
|
|
+ // const index = forms.musicSheetSoundList.findIndex(
|
|
|
+ // (ground: any) => item.value == ground.track
|
|
|
+ // )
|
|
|
+
|
|
|
+ // if (index > -1 && track == item.value) {
|
|
|
+ // item.disabled = false
|
|
|
+ // } else {
|
|
|
+ // item.disabled = true
|
|
|
+ // }
|
|
|
+ })
|
|
|
+ return partListNames || []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 反显声部
|
|
|
+ const showBackSubject = async (musicalInstrumentIdList: []) => {
|
|
|
+ try {
|
|
|
+ const {data} = await subjectPage({
|
|
|
+ page: 1,
|
|
|
+ rows: 999,
|
|
|
+ musicalInstrumentIdList: musicalInstrumentIdList
|
|
|
+ })
|
|
|
+ const tempList = data.rows || []
|
|
|
+ tempList.forEach((item: any) => {
|
|
|
+ forms.subjectIds.push(item.id + '')
|
|
|
+ })
|
|
|
+ } catch {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加原音
|
|
|
+ const createSys = (initData?: any) => {
|
|
|
+ forms.musicSheetSoundList_YY.push({
|
|
|
+ audioFileUrl: null, // 原音
|
|
|
+ track: null, // 轨道
|
|
|
+ ...initData
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 删除原音
|
|
|
+ const removeSys = (index: number) => {
|
|
|
+ dialog.warning({
|
|
|
+ title: '提示',
|
|
|
+ content: `是否确认删除此原音?`,
|
|
|
+ positiveText: '确定',
|
|
|
+ negativeText: '取消',
|
|
|
+ onPositiveClick: async () => {
|
|
|
+ const sound = forms.musicSheetSoundList_YY[index]
|
|
|
+ const track = sound.track
|
|
|
+ if (track != null) {
|
|
|
+ const selectIndex = forms.multiTracksSelection.indexOf(track)
|
|
|
+ if (selectIndex > -1) {
|
|
|
+ forms.multiTracksSelection.splice(selectIndex, 1)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ forms.musicSheetSoundList_YY.splice(index, 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const checkMultiTracks = (value: string) => {
|
|
|
+ if (!value) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (value === 'all') {
|
|
|
+ forms.multiTracksSelection = []
|
|
|
+ state.partListNames.forEach((next: any) => {
|
|
|
+ forms.multiTracksSelection.push(next.value)
|
|
|
+ })
|
|
|
+ } else if (value === 'invert') {
|
|
|
+ state.partListNames.forEach((next: any) => {
|
|
|
+ const indexOf = forms.multiTracksSelection.indexOf(next.value)
|
|
|
+ if (indexOf > -1) {
|
|
|
+ forms.multiTracksSelection.splice(indexOf, 1)
|
|
|
+ } else {
|
|
|
+ forms.multiTracksSelection.push(next.value)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else if (value === 'allUncheck') {
|
|
|
+ forms.multiTracksSelection = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const setOwnerName = () => {
|
|
|
+ if (forms.sourceType == 'PLATFORM') {
|
|
|
+ state.ownerName = ''
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!forms.musicSheetExtend || !forms.sourceType || !forms.musicSheetExtend?.userId) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const appId = forms.musicSheetExtend.applicationId
|
|
|
+ const app = state.appData.filter((next: any) => {
|
|
|
+ return next.id == appId
|
|
|
+ }) as any
|
|
|
+ if (app.length > 0) {
|
|
|
+ state.ownerName = app[0].appName
|
|
|
+ }
|
|
|
+ if (forms.sourceType == 'ORG') {
|
|
|
+ state.ownerName += '-' + forms.musicSheetExtend.organizationRole
|
|
|
+ } else if (forms.sourceType == 'PERSON') {
|
|
|
+ state.ownerName +=
|
|
|
+ '-' +
|
|
|
+ getMapValueByKey(forms.musicSheetExtend.clientType, new Map(Object.entries(clientType)))
|
|
|
+ if (forms.musicSheetExtend.userName) {
|
|
|
+ state.ownerName += '-' + forms.musicSheetExtend.userName
|
|
|
+ }
|
|
|
+ if (forms.musicSheetExtend.phone) {
|
|
|
+ state.ownerName += '(' + forms.musicSheetExtend.phone + ')'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const changeSubject = async (subjectIdList: []) => {
|
|
|
+ state.instrumentList = []
|
|
|
+ if (!subjectIdList || subjectIdList.length == 0) {
|
|
|
+ forms.musicalInstrumentIdList = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let enableFlag = null;
|
|
|
+ if (props.type === 'add') {
|
|
|
+ enableFlag = true;
|
|
|
+ }
|
|
|
+ let tempMusicalInstrumentIdList = [] as any
|
|
|
+ const {data} = await musicalInstrumentPage({page: 1, rows: 999, subjectIds: subjectIdList, enableFlag: enableFlag});
|
|
|
+ data.rows.map((item: any) => {
|
|
|
+ tempMusicalInstrumentIdList.push(item.id + '')
|
|
|
+ state.instrumentList.push(
|
|
|
+ {
|
|
|
+ label: item.name,
|
|
|
+ value: item.id + '',
|
|
|
+ disabled: !item.enableFlag
|
|
|
+ }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ forms.musicalInstrumentIdList = forms.musicalInstrumentIdList.filter((item: any) => {
|
|
|
+ return tempMusicalInstrumentIdList.includes(item)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(async () => {
|
|
|
+ state.loading = true
|
|
|
+ if (props.type === 'preview') {
|
|
|
+ state.previewMode = true
|
|
|
+ }
|
|
|
+ // 获取乐器信息
|
|
|
+ {
|
|
|
+ if (state.instrumentList && state.instrumentList.length > 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const {data} = await musicalInstrumentPage({page: 1, rows: 999})
|
|
|
+ const tempList = data.rows || []
|
|
|
+ tempList.forEach((item: any) => {
|
|
|
+ item.label = item.name
|
|
|
+ item.value = item.id + ''
|
|
|
+ item.disabled = !item.enableFlag
|
|
|
+
|
|
|
+ state.instrumentIdNameMap.set(item.id + '', item.name)
|
|
|
+
|
|
|
+ forms.musicSheetSoundList_YZ.push({
|
|
|
+ 'musicSheetId': props.data.id,
|
|
|
+ 'musicalInstrumentId': item.id + '',
|
|
|
+ 'musicalInstrumentName': item.name,
|
|
|
+ 'audioFileUrl': null,
|
|
|
+ 'audioPlayType': 'PLAY'
|
|
|
+ });
|
|
|
+ })
|
|
|
+ state.instrumentData = tempList
|
|
|
+ // state.instrumentList = tempList
|
|
|
+ } catch {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ state.subjectList = deepClone(props.subjectList)
|
|
|
+ state.subjectList.forEach((subject: any) => {
|
|
|
+ subject.disabled = !subject.enableFlag
|
|
|
+ })
|
|
|
+
|
|
|
+ // 初始化应用
|
|
|
+ {
|
|
|
+ const appKeys = Object.keys(appKey)
|
|
|
+
|
|
|
+ const {data} = await sysApplicationPage({page: 1, rows: 999, parentId: 0})
|
|
|
+ const tempList = data.rows || []
|
|
|
+ const filter = tempList.filter((next: any) => {
|
|
|
+ return appKeys.includes(next.appKey)
|
|
|
+ })
|
|
|
+ filter.forEach((item: any) => {
|
|
|
+ item.label = item.appName
|
|
|
+ item.value = item.id
|
|
|
+ })
|
|
|
+ state.appData = filter
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取分类信息
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ const {data} = await musicSheetCategoriesQueryTree({enable: true})
|
|
|
+ state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList')
|
|
|
+ } catch (e) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (props.type === 'edit' || props.type === 'preview') {
|
|
|
+ const detail = props.data
|
|
|
+ try {
|
|
|
+ const {data} = await musicSheetDetail({id: detail.id})
|
|
|
+ forms.details = data
|
|
|
+ forms.playMode = data.playMode
|
|
|
+ forms.xmlFileUrl = data.xmlFileUrl
|
|
|
+ forms.midiUrl = data.midiUrl
|
|
|
+ forms.name = data.name
|
|
|
+ // forms.musicTag = data.musicTag?.split(',')
|
|
|
+ forms.composer = data.composer
|
|
|
+ forms.playSpeed = data.playSpeed ? Number.parseFloat(data.playSpeed) : 100
|
|
|
+ // forms.showFingering = Number(data.showFingering)
|
|
|
+ // forms.canEvaluate = Number(data.canEvaluate)
|
|
|
+ // forms.notation = Number(data.notation)
|
|
|
+ // forms.auditVersion = Number(data.auditVersion)
|
|
|
+ // forms.sortNumber = data.sortNumber
|
|
|
+ forms.musicCover = data.musicCover
|
|
|
+ forms.remark = data.remark
|
|
|
+ forms.status = data.status
|
|
|
+ forms.musicSheetType = data.musicSheetType || 'SINGLE'
|
|
|
+ forms.sourceType = data.sourceType
|
|
|
+ forms.appAuditFlag = data.appAuditFlag ? 1 : 0
|
|
|
+ forms.midiFileUrl = data.midiFileUrl
|
|
|
+ forms.isShowFingering = data.isShowFingering
|
|
|
+ forms.isAllSubject = data.isAllSubject
|
|
|
+ forms.isUseSingSystemBeat = data.isUseSingSystemBeat
|
|
|
+ forms.isPlaySingBeat = data.isPlaySingBeat
|
|
|
+ forms.subjectIds = []
|
|
|
+ if (data.subjectIds) {
|
|
|
+ const subjectIds = data.subjectIds.split(',') || []
|
|
|
+ subjectIds.forEach((subjectId: any) => {
|
|
|
+ if (!forms.subjectIds.includes(subjectId)) {
|
|
|
+ forms.subjectIds.push(subjectId)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ forms.musicalInstrumentIdList = data.musicalInstrumentIds ? data.musicalInstrumentIds.split(',') : []
|
|
|
+ await changeSubject(forms.subjectIds)
|
|
|
+ forms.musicCategoryId = data.musicCategoryId
|
|
|
+ forms.audioType = data.audioType
|
|
|
+ forms.isPlayBeat = data.isPlayBeat
|
|
|
+ forms.isUseSystemBeat = data.isUseSystemBeat
|
|
|
+ // 获取渐变 和 是否多声部
|
|
|
+ try {
|
|
|
+ const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
|
|
|
+ forms.graduals = extConfigJson.gradualTimes || {}
|
|
|
+ forms.repeatedBeats = !!extConfigJson.repeatedBeats
|
|
|
+ forms.isEvxml = !!extConfigJson.isEvxml
|
|
|
+ forms.repeatedBeatsToSing = !!extConfigJson.repeatedBeatsToSing
|
|
|
+ } catch (error) {
|
|
|
+ }
|
|
|
+ forms.evaluationStandard = data.evaluationStandard
|
|
|
+ forms.musicSheetExtend = data.musicSheetExtend
|
|
|
+
|
|
|
+ state.musicSheetSoundList = data.musicSheetSoundList ? data.musicSheetSoundList : []
|
|
|
+
|
|
|
+ let musicSheetAccompanimentList = data.musicSheetAccompanimentList ? data.musicSheetAccompanimentList : []
|
|
|
+ musicSheetAccompanimentList.forEach((next: any) => {
|
|
|
+ let audioPlayType = next.audioPlayType;
|
|
|
+ if (audioPlayType && audioPlayType == 'SING') {
|
|
|
+ state.bSongFile = next.audioFileUrl;
|
|
|
+ } else {
|
|
|
+ state.musicSheetAccompanimentUrlList.push(next.audioFileUrl)
|
|
|
+ forms.musicSheetAccompanimentList.push(next)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 初始化演奏
|
|
|
+ for (let i = 0; i < state.musicSheetSoundList.length; i++) {
|
|
|
+ if (state.musicSheetSoundList[i].audioPlayType == 'SING') {
|
|
|
+ // 范唱 唱名
|
|
|
+ state.fSongFile = state.musicSheetSoundList[i].audioFileUrl
|
|
|
+ forms.solmizationFileUrl = state.musicSheetSoundList[i].solmizationFileUrl
|
|
|
+ } else {
|
|
|
+ if (forms.isAllSubject) {
|
|
|
+ forms.musicSheetSoundList_all_subject = state.musicSheetSoundList[i].audioFileUrl
|
|
|
+ } else {
|
|
|
+ // 乐器演奏原音
|
|
|
+ for (let j = 0; j < forms.musicSheetSoundList_YZ.length; j++) {
|
|
|
+ let musicalInstrumentId = state.musicSheetSoundList[i].musicalInstrumentId;
|
|
|
+ if (musicalInstrumentId && musicalInstrumentId == forms.musicSheetSoundList_YZ[j].musicalInstrumentId) {
|
|
|
+ forms.musicSheetSoundList_YZ[j].audioFileUrl = state.musicSheetSoundList[i].audioFileUrl
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ setOwnerName()
|
|
|
+ axios.get(data.xmlFileUrl).then((res: any) => {
|
|
|
+ if (res?.data) {
|
|
|
+ gradualData.list = getGradualLengthByXml(res?.data as any).filter(
|
|
|
+ (item: any) => item.length === 2
|
|
|
+ )
|
|
|
+ state.partListNames = getPartListNames(res?.data as any) as any
|
|
|
+
|
|
|
+ // 初始化音轨和原音
|
|
|
+ if (data.multiTracksSelection) {
|
|
|
+ data.multiTracksSelection = data.multiTracksSelection.toLocaleUpperCase()
|
|
|
+ }
|
|
|
+ if (!data.multiTracksSelection || data.multiTracksSelection.trim() == '' || data.multiTracksSelection.trim() == 'NULL') {
|
|
|
+ forms.multiTracksSelection = ['']
|
|
|
+ } else {
|
|
|
+ forms.multiTracksSelection = data.multiTracksSelection.split(',')
|
|
|
+ }
|
|
|
+ let names = state.partListNames.map((next: any) => next.label)
|
|
|
+ forms.multiTracksSelection = names.filter((next: any) => forms.multiTracksSelection.includes(next.toLocaleUpperCase()))
|
|
|
+
|
|
|
+ if (state.musicSheetSoundList.length === 1 && state.musicSheetSoundList[0].track === 'P1') {
|
|
|
+ forms.musicSheetSoundList_YY.push({
|
|
|
+ audioFileUrl: state.musicSheetSoundList[0].audioFileUrl, // 原音
|
|
|
+ track: state.partListNames[0].value || '', // 轨道
|
|
|
+ audioPlayType: 'PLAY'
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const tracks = [] as any
|
|
|
+ state.partListNames.forEach((item: any) => {
|
|
|
+ let audioFileUrl = null
|
|
|
+ state.musicSheetSoundList.forEach((next: any) => {
|
|
|
+ let track = next.track ? next.track : ''
|
|
|
+ if (track.trim() == item.value) {
|
|
|
+ audioFileUrl = next.audioFileUrl
|
|
|
+ }
|
|
|
+ })
|
|
|
+ forms.musicSheetSoundList_YY.push({
|
|
|
+ audioFileUrl: audioFileUrl, // 原音
|
|
|
+ track: item.value, // 轨道
|
|
|
+ audioPlayType: 'PLAY'
|
|
|
+ })
|
|
|
+ tracks.push(item.value)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 处理没有声轨,但有原音
|
|
|
+ if (data.musicSheetType == 'CONCERT') {
|
|
|
+ state.musicSheetSoundList.forEach((next: any) => {
|
|
|
+ if (next.track && !tracks.includes(next.track.trim()) && next.audioPlayType == 'PLAY') {
|
|
|
+ forms.musicSheetSoundList_YY.push({
|
|
|
+ audioFileUrl: next.audioFileUrl, // 原音
|
|
|
+ track: next.track ? next.track.trim() : '', // 轨道
|
|
|
+ audioPlayType: 'PLAY'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 新增只能使用启用状态的数据
|
|
|
+ state.subjectList = state.subjectList.filter((next: any) => {
|
|
|
+ return next.enableFlag == true
|
|
|
+ })
|
|
|
+ // state.instrumentList = state.instrumentList.filter((next: any) => {
|
|
|
+ // return next.enableFlag == true
|
|
|
+ // })
|
|
|
+ }
|
|
|
+ state.loading = false
|
|
|
+ })
|
|
|
+
|
|
|
+ return () => (
|
|
|
+ <div style="background: #fff; padding-top: 12px">
|
|
|
+ <NSpin show={state.loading}>
|
|
|
+ <NForm
|
|
|
+ class={styles.formContainer}
|
|
|
+ model={forms}
|
|
|
+ ref={formsRef}
|
|
|
+ label-placement="left"
|
|
|
+ label-width="150"
|
|
|
+ disabled={state.previewMode}
|
|
|
+ >
|
|
|
+ <NAlert showIcon={false} style={{marginBottom: '12px'}}>
|
|
|
+ 基本信息
|
|
|
+ </NAlert>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="曲目名称"
|
|
|
+ path="name"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入曲目名称',
|
|
|
+ trigger: ['input', 'blur']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NInput
|
|
|
+ v-model:value={forms.name}
|
|
|
+ placeholder="请输入曲目名称"
|
|
|
+ maxlength={50}
|
|
|
+ showCount
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="音乐人"
|
|
|
+ path="composer"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入音乐人',
|
|
|
+ trigger: ['input', 'blur']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NInput
|
|
|
+ v-model:value={forms.composer}
|
|
|
+ placeholder="请输入音乐人名称"
|
|
|
+ showCount
|
|
|
+ maxlength={14}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="曲目封面"
|
|
|
+ path="musicCover"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请上传曲目封面',
|
|
|
+ trigger: ['input', 'blur']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'封面图'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ accept=".jpg,.jpeg,.png"
|
|
|
+ tips="请上传大小1M以内的JPG、PNG图片"
|
|
|
+ size={1}
|
|
|
+ v-model:fileList={forms.musicCover}
|
|
|
+ cropper
|
|
|
+ bucketName="cbs"
|
|
|
+ options={{
|
|
|
+ autoCrop: true, //是否默认生成截图框
|
|
|
+ enlarge: 2, // 图片放大倍数
|
|
|
+ autoCropWidth: 200, //默框高度
|
|
|
+ fixedBox: true, //是否固定截图框大认生成截图框宽度
|
|
|
+ autoCropHeight: 200, //默认生成截图小 不允许改变
|
|
|
+ previewsCircle: false, //预览图是否是原圆形
|
|
|
+ title: '曲目封面'
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="曲目分类"
|
|
|
+ path="musicCategoryId"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择曲目分类',
|
|
|
+ trigger: ['change']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NCascader
|
|
|
+ valueField="id"
|
|
|
+ labelField="name"
|
|
|
+ children-field="musicSheetCategoriesList"
|
|
|
+ placeholder="请选择分类"
|
|
|
+ v-model:value={forms.musicCategoryId}
|
|
|
+ options={state.musicSheetCategories}
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="作者属性"
|
|
|
+ path="sourceType"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择作者属性',
|
|
|
+ trigger: 'change'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NSelect
|
|
|
+ v-model:value={forms.sourceType}
|
|
|
+ options={getSelectDataFromObj(musicSheetSourceType)}
|
|
|
+ placeholder="请选择作者属性"
|
|
|
+ onUpdateValue={() => {
|
|
|
+ // 发送变化,清理选择的所属人信息
|
|
|
+ forms.musicSheetExtend = {}
|
|
|
+ state.ownerName = null
|
|
|
+ // forms.musicSheetExtend.userId = null
|
|
|
+ // forms.musicSheetExtend.userName = null
|
|
|
+ // forms.musicSheetExtend.applicationId = null
|
|
|
+ // forms.musicSheetExtend.organizationRoleId = null
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ {forms.sourceType === 'PERSON' && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="所属人"
|
|
|
+ path="musicSheetExtend.userId"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择曲目所属人',
|
|
|
+ trigger: ['input', 'change']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NButton
|
|
|
+ disabled={state.previewMode || !forms.sourceType}
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ //v-auth="orchestraSubsidyStandard/update1597887579789053953"
|
|
|
+ onClick={() => {
|
|
|
+ state.showMusicSheetOwnerDialog = true
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {state.ownerName ? state.ownerName : '请选择所属人'}
|
|
|
+ </NButton>
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ {forms.sourceType === 'ORG' && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="所属人"
|
|
|
+ path="musicSheetExtend.organizationRoleId"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择曲目所属机构',
|
|
|
+ trigger: ['input', 'change']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NButton
|
|
|
+ disabled={state.previewMode || !forms.sourceType}
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ //v-auth="orchestraSubsidyStandard/update1597887579789053953"
|
|
|
+ onClick={() => {
|
|
|
+ state.showMusicSheetOwnerDialog = true
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {state.ownerName ? state.ownerName : '请选择所属机构'}
|
|
|
+ </NButton>
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="审核版本"
|
|
|
+ path="appAuditFlag"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择审核版本',
|
|
|
+ trigger: 'change',
|
|
|
+ type: 'number'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NSelect
|
|
|
+ options={
|
|
|
+ [
|
|
|
+ {
|
|
|
+ label: '是',
|
|
|
+ value: 1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '否',
|
|
|
+ value: 0
|
|
|
+ }
|
|
|
+ ] as any
|
|
|
+ }
|
|
|
+ v-model:value={forms.appAuditFlag}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NAlert showIcon={false} style={{marginBottom: '12px'}}>
|
|
|
+ 曲目设置
|
|
|
+ </NAlert>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="播放模式"
|
|
|
+ path="playMode"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择播放模式'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup
|
|
|
+ v-model:value={forms.playMode}
|
|
|
+ onUpdateValue={(value: string | number | boolean) => {
|
|
|
+ if (value === 'MP3') {
|
|
|
+ forms.playMode = 'MP3'
|
|
|
+ } else {
|
|
|
+ forms.playMode = 'MIDI'
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <NRadio value="MP3">MP3</NRadio>
|
|
|
+ <NRadio value="MIDI">MID</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ {forms.playMode === 'MP3' && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="伴奏类型"
|
|
|
+ path="audioType"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择伴奏类型'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.audioType}>
|
|
|
+ <NRadio value={'HOMEMODE'}>自制伴奏</NRadio>
|
|
|
+ <NRadio value={'COMMON'}>普通伴奏</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="上传XML"
|
|
|
+ path="xmlFileUrl"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择上传XML',
|
|
|
+ trigger: ['change', 'input']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'XML文件'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={30}
|
|
|
+ key={'xmlFileUrl'}
|
|
|
+ v-model:fileList={forms.xmlFileUrl}
|
|
|
+ tips="仅支持上传.xml/.mxml格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".xml,.mxml,.evxml"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ text="点击上传XML文件"
|
|
|
+ onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
|
|
|
+ onRemove={() => {
|
|
|
+ forms.multiTracksSelection = []
|
|
|
+ state.partListNames = []
|
|
|
+ forms.musicSheetSoundList_YY = []
|
|
|
+ // forms.musicalInstrumentIdList = []
|
|
|
+ // forms.subjectIds = []
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="评分标准"
|
|
|
+ path="evaluationStandard"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.evaluationStandard}>
|
|
|
+ <NRadio value={'FREQUENCY'}>标准评测</NRadio>
|
|
|
+ <NRadio value={'AMPLITUDE'}>打击乐(振幅)</NRadio>
|
|
|
+ <NRadio value={'DECIBELS'}>节奏(分贝)</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="谱面渲染"
|
|
|
+ path="musicSheetType"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择谱面渲染',
|
|
|
+ trigger: 'change'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.musicSheetType}>
|
|
|
+ <NRadio value={'SINGLE'}>多声轨</NRadio>
|
|
|
+ <NRadio value={'CONCERT'}>单声轨</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="适用声部"
|
|
|
+ path="isAllSubject"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择适用声部',
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.isAllSubject}>
|
|
|
+ <NRadio value={false}>部分声部</NRadio>
|
|
|
+ <NRadio value={true}>全部声部</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ {!forms.isAllSubject && (
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="可用声部"
|
|
|
+ path="subjectIds"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择可用声部',
|
|
|
+ trigger: 'change',
|
|
|
+ type: 'array'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NSelect
|
|
|
+ v-model:value={forms.subjectIds}
|
|
|
+ options={state.subjectList}
|
|
|
+ multiple
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ placeholder="请选择可用声部"
|
|
|
+ maxTagCount={2}
|
|
|
+ onUpdateValue={async (val: any) => {
|
|
|
+ await changeSubject(val)
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="可用乐器"
|
|
|
+ path="musicalInstrumentIdList"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择可用乐器',
|
|
|
+ trigger: 'change',
|
|
|
+ type: 'array'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NSelect
|
|
|
+ placeholder="请选择可用乐器"
|
|
|
+ options={state.instrumentList}
|
|
|
+ v-model:value={forms.musicalInstrumentIdList}
|
|
|
+ clearable
|
|
|
+ multiple
|
|
|
+ maxTagCount={2}
|
|
|
+ onUpdateValue={async (value: any) => {
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ )}
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="速度"
|
|
|
+ path="playSpeed"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请输入速度'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NInputNumber
|
|
|
+ placeholder="请输入速度"
|
|
|
+ v-model:value={forms.playSpeed}
|
|
|
+ min={0}
|
|
|
+ style="width:100%"
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={1}>
|
|
|
+ <NFormItemGi
|
|
|
+ label={`${forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'}`}
|
|
|
+ path="multiTracksSelection"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: `请选择${
|
|
|
+ forms.musicSheetType === 'SINGLE' ? '页面渲染声轨' : '用户可切换声轨'
|
|
|
+ }`,
|
|
|
+ trigger: 'change',
|
|
|
+ type: 'array'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NGrid style="padding-top: 4px;">
|
|
|
+ <NGi span={24}>
|
|
|
+ <NRadioGroup
|
|
|
+ v-model:value={state.multiTracks}
|
|
|
+ onUpdateValue={(value) => {
|
|
|
+ checkMultiTracks(value)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <NRadio value={'all'}>全选</NRadio>
|
|
|
+ <NRadio value={'allUncheck'}>重置</NRadio>
|
|
|
+ <NRadio value={'invert'}>反选</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NGi>
|
|
|
+ {state.partListNames && state.partListNames.length > 0 && (
|
|
|
+ <NGi span={24} style={'margin-top:5px'}>
|
|
|
+ <NFormItemGi
|
|
|
+ label=""
|
|
|
+ path="multiTracksSelection"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NCheckboxGroup v-model:value={forms.multiTracksSelection}>
|
|
|
+ <NGrid yGap={2} cols={4}>
|
|
|
+ {state.partListNames.map((item: any) => (
|
|
|
+ <NGi>
|
|
|
+ <NCheckbox value={item.value} label={item.label}/>
|
|
|
+ </NGi>
|
|
|
+ ))}
|
|
|
+ </NGrid>
|
|
|
+ </NCheckboxGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGi>
|
|
|
+ )}
|
|
|
+ </NGrid>
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+
|
|
|
+
|
|
|
+ <NAlert showIcon={false} style={{marginBottom: '12px'}}>
|
|
|
+ 演唱文件
|
|
|
+ </NAlert>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="是否播放节拍器"
|
|
|
+ path="isPlaySingBeat"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择是否播放节拍器'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.isPlaySingBeat}>
|
|
|
+ <NRadio value={true}>是</NRadio>
|
|
|
+ <NRadio value={false}>否</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ {forms.isPlaySingBeat && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="播放方式"
|
|
|
+ path="isUseSingSystemBeat"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择播放方式'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.isUseSingSystemBeat}>
|
|
|
+ <NRadio value={true}>系统节拍器</NRadio>
|
|
|
+ <NRadio value={false}>MP3节拍器</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="重复节拍时长"
|
|
|
+ path="repeatedBeatsToSing"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择是否重复节拍时长'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.repeatedBeatsToSing}>
|
|
|
+ <NRadio value={true}>是</NRadio>
|
|
|
+ <NRadio value={false}>否</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="上传范唱"
|
|
|
+ path="fSongFile"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择上传范唱',
|
|
|
+ trigger: ['change', 'input']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'上传范唱'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={30}
|
|
|
+ key={'xmlFileUrl'}
|
|
|
+ v-model:fileList={state.fSongFile}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ text="点击上传范唱文件"
|
|
|
+ onRemove={() => {
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="上传伴唱"
|
|
|
+ path="bSongFile"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择上传.MID格式文件'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'上传伴唱'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={30}
|
|
|
+ v-model:fileList={state.bSongFile}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ text="点击上传伴唱文件"
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="上传唱名"
|
|
|
+ path="solmizationFileUrl"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择上传唱名',
|
|
|
+ trigger: ['change', 'input']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'上传唱名'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={30}
|
|
|
+ key={'xmlFileUrl'}
|
|
|
+ v-model:fileList={forms.solmizationFileUrl}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ text="点击上传唱名文件"
|
|
|
+ onRemove={() => {
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NAlert showIcon={false} style={{marginBottom: '12px'}}>
|
|
|
+ 演奏文件
|
|
|
+ </NAlert>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="是否播放节拍器"
|
|
|
+ path="isPlayBeat"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择是否播放节拍器'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.isPlayBeat}>
|
|
|
+ <NRadio value={true}>是</NRadio>
|
|
|
+ <NRadio value={false}>否</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ {forms.isPlayBeat && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="播放方式"
|
|
|
+ path="isUseSystemBeat"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择播放方式'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.isUseSystemBeat}>
|
|
|
+ <NRadio value={true}>系统节拍器</NRadio>
|
|
|
+ <NRadio value={false}>MP3节拍器</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ <NFormItemGi
|
|
|
+ label="重复节拍时长"
|
|
|
+ path="repeatedBeats"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择是否重复节拍时长'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.repeatedBeats}>
|
|
|
+ <NRadio value={true}>是</NRadio>
|
|
|
+ <NRadio value={false}>否</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ <NFormItemGi
|
|
|
+ label="是否显示指法"
|
|
|
+ path="isShowFingering"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请选择是否显示指法'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NRadioGroup v-model:value={forms.isShowFingering}>
|
|
|
+ <NRadio value={true}>是</NRadio>
|
|
|
+ <NRadio value={false}>否</NRadio>
|
|
|
+ </NRadioGroup>
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ <NGrid cols={2}>
|
|
|
+ {forms.playMode === 'MP3' && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="上传伴奏"
|
|
|
+ path="musicSheetAccompanimentList"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择上传.mp3'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={30}
|
|
|
+ v-model:imageList={state.musicSheetAccompanimentUrlList}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ text="点击上传伴奏文件"
|
|
|
+ max={1}
|
|
|
+ desc={'上传伴奏文件'}
|
|
|
+ onUpload:success={(file) => {
|
|
|
+ state.musicSheetAccompanimentUrls = [state.musicSheetAccompanimentUrls, file.url].filter(Boolean).join(',')
|
|
|
+ state.musicSheetAccompanimentUrlList = state.musicSheetAccompanimentUrls?.split(',').filter(Boolean)
|
|
|
+
|
|
|
+ // 清除伴奏
|
|
|
+ // forms.musicSheetAccompanimentList = forms.musicSheetAccompanimentList.filter((next: any) => {
|
|
|
+ // return next.audioPlayType == 'SING'
|
|
|
+ // })
|
|
|
+ forms.musicSheetAccompanimentList = []
|
|
|
+ for (let i = 0; i < state.musicSheetAccompanimentUrlList.length; i++) {
|
|
|
+ forms.musicSheetAccompanimentList.push({
|
|
|
+ audioFileUrl: state.musicSheetAccompanimentUrlList[i],
|
|
|
+ sortNumber: i + 1,
|
|
|
+ audioPlayType: 'PLAY'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onRemove={() => {
|
|
|
+ state.musicSheetAccompanimentUrlList = []
|
|
|
+ state.musicSheetAccompanimentUrls = ''
|
|
|
+ }}
|
|
|
+ // onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
|
|
|
+ multiple={true}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ {forms.isAllSubject && forms.musicSheetType == 'SINGLE' && (
|
|
|
+ <NFormItemGi
|
|
|
+ label="上传原音"
|
|
|
+ path="musicSheetSoundList_all_subject"
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择上传原音',
|
|
|
+ trigger: ['change', 'input']
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'上传原音'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={30}
|
|
|
+ max={1}
|
|
|
+ v-model:fileList={forms.musicSheetSoundList_all_subject}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ text="点击上传原音"
|
|
|
+ onRemove={() => {
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ </NGrid>
|
|
|
+
|
|
|
+ {/*渐变速*/}
|
|
|
+ {!!gradualData.list.length && (
|
|
|
+ <>
|
|
|
+ <NAlert showIcon={false} type="info">
|
|
|
+ 识别到共1处渐变速度,请输入Dorico对应小节时间信息
|
|
|
+ </NAlert>
|
|
|
+ <NFormItem label="rit." required style={{marginTop: '10px'}}>
|
|
|
+ <NSpace vertical>
|
|
|
+ {gradualData.list.map((n: any, ni: number) => (
|
|
|
+ <NInputGroup>
|
|
|
+ <NFormItem
|
|
|
+ path={`graduals.${n[0].measureIndex}`}
|
|
|
+ rule={[
|
|
|
+ {required: true, message: '请输入合奏曲目时间'},
|
|
|
+ {
|
|
|
+ pattern: /^((\d{2}):?){2,3}$/,
|
|
|
+ message: '请输入正确的曲目时间',
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NInputGroup>
|
|
|
+ <NInputGroupLabel>{n[0].measureIndex}小节开始</NInputGroupLabel>
|
|
|
+ <NInput
|
|
|
+ placeholder="00:00:00"
|
|
|
+ v-model:value={forms.graduals[n[0].measureIndex]}
|
|
|
+ ></NInput>
|
|
|
+ </NInputGroup>
|
|
|
+ </NFormItem>
|
|
|
+ <div style={{lineHeight: '30px', padding: '0 4px'}}>~</div>
|
|
|
+ <NFormItem
|
|
|
+ path={`graduals.${n[1].measureIndex}`}
|
|
|
+ rule={[
|
|
|
+ {required: true, message: '请输入合奏曲目时间'},
|
|
|
+ {
|
|
|
+ pattern: /^((\d{2}):?){2,3}$/,
|
|
|
+ message: '请输入正确的曲目时间',
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NInputGroup>
|
|
|
+ <NInput
|
|
|
+ placeholder="00:00:00"
|
|
|
+ v-model:value={forms.graduals[n[1].measureIndex]}
|
|
|
+ ></NInput>
|
|
|
+ <NInputGroupLabel>{n[1].measureIndex}小节结束</NInputGroupLabel>
|
|
|
+ </NInputGroup>
|
|
|
+ </NFormItem>
|
|
|
+ </NInputGroup>
|
|
|
+ ))}
|
|
|
+ </NSpace>
|
|
|
+ </NFormItem>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ {/*独奏*/}
|
|
|
+ {forms.musicSheetType == 'SINGLE' && forms.playMode === 'MP3' && !forms.isAllSubject && forms.musicSheetSoundList_YZ.map((item: any, index: any) => {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {forms.musicalInstrumentIdList.includes(item.musicalInstrumentId) && (
|
|
|
+ <NGrid class={styles.audioSection}>
|
|
|
+ <NFormItemGi
|
|
|
+ span={12}
|
|
|
+ label={item.musicalInstrumentName}
|
|
|
+ path={`musicSheetSoundList_YZ[${index}].audioFileUrl`}
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: `请上传乐器演奏文件`
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'乐器演奏文件'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={100}
|
|
|
+ v-model:fileList={item.audioFileUrl}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ text={"点击上传MP3文件"}
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ </NGrid>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )
|
|
|
+ })
|
|
|
+ }
|
|
|
+ {/*合奏*/}
|
|
|
+ {forms.musicSheetType == 'CONCERT' && forms.playMode === 'MP3' && forms.musicSheetSoundList_YY.length > 0 && (
|
|
|
+ <>
|
|
|
+ {forms.musicSheetSoundList_YY.map((item: any, index: number) => {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {(!containOther(item.track) ||
|
|
|
+ (item.track?.toLocaleUpperCase?.() != 'COMMON' &&
|
|
|
+ forms.multiTracksSelection.includes(item.track))) && (
|
|
|
+ <NGrid
|
|
|
+ class={styles.audioSection}
|
|
|
+ // v-show={forms.multiTracksSelection.indexOf(item.track) > -1}
|
|
|
+ >
|
|
|
+ <NFormItemGi
|
|
|
+ span={12}
|
|
|
+ label="原音"
|
|
|
+ path={`musicSheetSoundList_YY[${index}].audioFileUrl`}
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ // required: forms.multiTracksSelection.indexOf(forms.musicSheetSoundList_YY[index].audioFileUrl) > -1,
|
|
|
+ required: true,
|
|
|
+ message: `请上传${
|
|
|
+ item.track ? item.track + '的' : '第' + (index + 1) + '个'
|
|
|
+ }原音`
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <UploadFile
|
|
|
+ desc={'原音文件'}
|
|
|
+ disabled={state.previewMode}
|
|
|
+ size={100}
|
|
|
+ v-model:fileList={item.audioFileUrl}
|
|
|
+ tips="仅支持上传.mp3格式文件"
|
|
|
+ listType="image"
|
|
|
+ accept=".mp3"
|
|
|
+ bucketName="cloud-coach"
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ {state.partListNames.length > 0 && (
|
|
|
+ <NFormItemGi
|
|
|
+ span={12}
|
|
|
+ label="所属轨道"
|
|
|
+ path={`musicSheetSoundList_YY[${index}].track`}
|
|
|
+ rule={[
|
|
|
+ {
|
|
|
+ required: false,
|
|
|
+ message: '请选择所属轨道'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <NSelect
|
|
|
+ placeholder="请选择所属轨道"
|
|
|
+ value={item.track}
|
|
|
+ options={initPartsListStatus(item.track)}
|
|
|
+ onUpdateValue={(value: any) => {
|
|
|
+ const track = item.track
|
|
|
+
|
|
|
+ if (track) {
|
|
|
+ // 声轨交换
|
|
|
+ forms.musicSheetSoundList_YY.forEach((next: any) => {
|
|
|
+ if (next.track == value) {
|
|
|
+ next.track = track
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const index = forms.multiTracksSelection.indexOf(item.track)
|
|
|
+ forms.multiTracksSelection.splice(index, 1)
|
|
|
+ } else {
|
|
|
+ forms.musicSheetSoundList_YY = forms.musicSheetSoundList_YY.filter(
|
|
|
+ (next: any) => {
|
|
|
+ return next.track != value
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ if (value && !forms.multiTracksSelection.includes(value)) {
|
|
|
+ forms.multiTracksSelection.push(value)
|
|
|
+ }
|
|
|
+ item.track = value
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NFormItemGi>
|
|
|
+ )}
|
|
|
+ <NGi class={styles.btnRemove}>
|
|
|
+ <NButton
|
|
|
+ type="primary"
|
|
|
+ text
|
|
|
+ // disabled={forms.musicSheetSoundList_YY.length === 1}
|
|
|
+ onClick={() => removeSys(index)}
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </NButton>
|
|
|
+ </NGi>
|
|
|
+ </NGrid>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )
|
|
|
+ })}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ </NForm>
|
|
|
+ </NSpin>
|
|
|
+ {props.type !== 'preview' && (
|
|
|
+ <NSpace justify="end" style="padding-top:12px">
|
|
|
+ <NButton type="default" onClick={() => emit('close')}>
|
|
|
+ 取消
|
|
|
+ </NButton>
|
|
|
+ <NButton
|
|
|
+ type="primary"
|
|
|
+ onClick={() => onSubmit()}
|
|
|
+ loading={btnLoading.value}
|
|
|
+ disabled={btnLoading.value}
|
|
|
+ >
|
|
|
+ 确认
|
|
|
+ </NButton>
|
|
|
+ </NSpace>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <NModal
|
|
|
+ v-model:show={state.showMusicSheetOwnerDialog}
|
|
|
+ preset="dialog"
|
|
|
+ showIcon={false}
|
|
|
+ maskClosable={false}
|
|
|
+ title="所属人"
|
|
|
+ style={{width: '800px'}}
|
|
|
+ >
|
|
|
+ <MusicSheetOwnerDialog
|
|
|
+ musicSheetExtend={forms.musicSheetExtend}
|
|
|
+ sourceType={forms.sourceType}
|
|
|
+ appData={state.appData}
|
|
|
+ onClose={() => {
|
|
|
+ state.showMusicSheetOwnerDialog = false
|
|
|
+ }}
|
|
|
+ onChoseMusicSheetOwnerData={(musicSheetOwnerData) => {
|
|
|
+ forms.musicSheetExtend = {
|
|
|
+ ...musicSheetOwnerData
|
|
|
+ }
|
|
|
+ setOwnerName()
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NModal>
|
|
|
+
|
|
|
+ <NModal
|
|
|
+ class={styles.productModal}
|
|
|
+ title="自动生成曲谱图片"
|
|
|
+ v-model:show={state.productOpen}
|
|
|
+ preset="dialog"
|
|
|
+ closeOnEsc={false}
|
|
|
+ maskClosable={false}
|
|
|
+ showIcon={false}
|
|
|
+ >
|
|
|
+ <MusicCreateImg
|
|
|
+ xmlFileUrl={forms.xmlFileUrl || ''}
|
|
|
+ onClose={() => (state.productOpen = false)}
|
|
|
+ onConfirm={async (item: any) => {
|
|
|
+ // 保存
|
|
|
+ try {
|
|
|
+ forms.musicImg = item.musicImg
|
|
|
+ forms.musicFirstImg = item.musicFirstImg
|
|
|
+ forms.musicJianImg = item.musicJianImg
|
|
|
+ onSubmit()
|
|
|
+ } catch (e: any) {
|
|
|
+ //
|
|
|
+ console.log(e, 'e')
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ state.isAutoSave = false
|
|
|
+ }, 50)
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NModal>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+})
|