form.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. <template>
  2. <div>
  3. <el-form ref="form" :model="form" label-width="150px">
  4. <el-form-item
  5. prop="sysMusicScore.name"
  6. label="曲名"
  7. :rules="[{ required: true, message: '请输入曲名' }]"
  8. >
  9. <el-input placeholder="请输入曲名" v-model="form.sysMusicScore.name" />
  10. </el-form-item>
  11. <el-form-item
  12. prop="sysMusicScore.musicScoreCategoriesId"
  13. label="分类"
  14. :rules="[{ required: true, message: '请选择分类' }]"
  15. >
  16. <el-cascader
  17. v-model="form.sysMusicScore.musicScoreCategoriesId"
  18. style="width: 100%"
  19. :options="tree"
  20. placeholder="请选择分类"
  21. :props="treeProps"
  22. ></el-cascader>
  23. <!-- <el-select style="width: 100%!important;" v-model="form.sysMusicScore.musicScoreCategoriesId" placeholder="请选择声部">
  24. <el-option
  25. v-for="item in selects.subjects"
  26. :value="item.id"
  27. :label="item.name"
  28. :key="item.id"
  29. ></el-option>
  30. </el-select> -->
  31. </el-form-item>
  32. <!-- 是否收费 免费 收费 -->
  33. <el-form-item
  34. prop="rankIdType"
  35. label="是否收费"
  36. :rules="[{ required: true, message: '请选择是否收费' }]"
  37. >
  38. <el-select
  39. style="width: 100% !important"
  40. v-model="form.rankIdType"
  41. placeholder="请选择是否收费"
  42. @change="rankChange"
  43. >
  44. <!-- <el-option
  45. :value="item.id"
  46. :label="item.name"
  47. v-for="item in memberRankList"
  48. :key="item.id"
  49. ></el-option> -->
  50. <el-option :value="0" label="免费"></el-option>
  51. <el-option :value="1" label="收费"></el-option>
  52. </el-select>
  53. </el-form-item>
  54. <el-form-item
  55. prop="sysMusicScore.isOpenMetronome"
  56. label="节拍器"
  57. :rules="[{ required: true, message: '请选择节拍器' }]"
  58. >
  59. <template slot="label">
  60. <span>
  61. 节拍器
  62. <el-tooltip placement="top" popper-class="mTooltip">
  63. <div slot="content">是否播放系统自带节拍器</div>
  64. <i
  65. class="el-icon-question"
  66. style="font-size: 18px; color: #f56c6c"
  67. ></i>
  68. </el-tooltip>
  69. </span>
  70. </template>
  71. <el-select
  72. style="width: 100% !important"
  73. v-model="form.sysMusicScore.isOpenMetronome"
  74. placeholder="请选择节拍器"
  75. >
  76. <!-- <el-option
  77. :value="item.id"
  78. :label="item.name"
  79. v-for="item in memberRankList"
  80. :key="item.id"
  81. ></el-option> -->
  82. <el-option :value="0" label="不播放"></el-option>
  83. <el-option :value="1" label="播放"></el-option>
  84. </el-select>
  85. </el-form-item>
  86. <el-form-item label="重复节拍时长">
  87. <template slot="label">
  88. <span>
  89. 重复节拍时长
  90. <el-tooltip placement="top" popper-class="mTooltip">
  91. <div slot="content">2/4拍类似的节拍器是否重复时长</div>
  92. <i
  93. class="el-icon-question"
  94. style="font-size: 18px; color: #f56c6c"
  95. ></i>
  96. </el-tooltip>
  97. </span>
  98. </template>
  99. <el-select
  100. style="width: 100% !important"
  101. v-model="form.repeatedBeats"
  102. placeholder="请选择是否重复节拍器时长"
  103. >
  104. <el-option :value="0" label="不重复"></el-option>
  105. <el-option :value="1" label="重复"></el-option>
  106. </el-select>
  107. </el-form-item>
  108. <!-- <el-form-item
  109. prop="sysMusicScore.clientType"
  110. label="客户端类型"
  111. :rules="[{ required: true, message: '请选择客户端类型' }]"
  112. >
  113. <el-select
  114. style="width: 100% !important"
  115. v-model="form.sysMusicScore.clientType"
  116. placeholder="请选择客户端类型"
  117. >
  118. <el-option value="NETWORK_ROOM" label="网络教室"></el-option>
  119. <el-option value="SMART_PRACTICE" label="云教练"></el-option>
  120. </el-select>
  121. </el-form-item> -->
  122. <!-- <el-form-item
  123. prop="sysMusicScore.renderFrom"
  124. label="渲染模式"
  125. :rules="[{ required: true, message: '请选择渲染模式' }]"
  126. >
  127. <el-select
  128. style="width: 100% !important"
  129. v-model="form.sysMusicScore.renderFrom"
  130. placeholder="请选择渲染模式"
  131. >
  132. <el-option value="H5" label="H5"></el-option>
  133. <el-option value="APP" label="原生"></el-option>
  134. </el-select>
  135. </el-form-item> -->
  136. <el-form-item
  137. prop="sysMusicScore.playMode"
  138. label="播放模式"
  139. :rules="[{ required: true, message: '请选择播放模式' }]"
  140. >
  141. <el-select
  142. style="width: 100% !important"
  143. v-model="form.sysMusicScore.playMode"
  144. placeholder="请选择播放模式"
  145. >
  146. <el-option value="MP3" label="MP3播放"></el-option>
  147. <el-option value="XML" label="XML播放"></el-option>
  148. </el-select>
  149. </el-form-item>
  150. <el-form-item
  151. prop="sysMusicScore.enableEvaluation"
  152. label="支持评测"
  153. :rules="[{ required: true, message: '请选择支持评测' }]"
  154. >
  155. <el-radio-group v-model="form.sysMusicScore.enableEvaluation">
  156. <el-radio :label="1">是</el-radio>
  157. <el-radio :label="0">否</el-radio>
  158. </el-radio-group>
  159. </el-form-item>
  160. <el-form-item :prop="`sysMusicScore.subjectId`" label="声部">
  161. <!-- :rules="[{required: true, message: '请选择声部'}]" -->
  162. <el-select
  163. style="width: 100% !important"
  164. v-model="form.sysMusicScore.subjectId"
  165. clearable
  166. placeholder="请选择声部"
  167. >
  168. <el-option
  169. v-for="item in selects.subjects"
  170. :value="item.id"
  171. :label="item.name"
  172. :key="item.id"
  173. :disabled="hasSubjectId(item.id)"
  174. ></el-option>
  175. </el-select>
  176. </el-form-item>
  177. <el-form-item
  178. :prop="`sysMusicScore.isShowFingering`"
  179. label="指法展示"
  180. :rules="[{ required: true, message: '请选择是否展示指法' }]"
  181. >
  182. <el-select
  183. style="width: 100% !important"
  184. v-model="form.sysMusicScore.isShowFingering"
  185. placeholder="请选择是否展示指法"
  186. >
  187. <el-option :value="true" label="是"></el-option>
  188. <el-option :value="false" label="否"></el-option>
  189. </el-select>
  190. </el-form-item>
  191. <el-form-item
  192. prop="sysMusicScore.order"
  193. label="排序"
  194. :rules="[
  195. { required: true, message: '请输入排序' },
  196. {
  197. pattern: /^([1-9]\d*|[0]{1,1})$/,
  198. message: '请输入正确的排序',
  199. trigger: 'blur',
  200. },
  201. ]"
  202. >
  203. <el-input placeholder="请输入排序" v-model="form.sysMusicScore.order" />
  204. </el-form-item>
  205. <el-form-item
  206. label="MusicXML"
  207. :prop="`sysMusicScore.xmlUrl`"
  208. :rules="[{ required: true, message: '请选择MusicXML文件' }]"
  209. >
  210. <singe-file-upload
  211. tips="仅支持上传 xml 格式文件"
  212. accept=".xml"
  213. v-model="form.sysMusicScore.xmlUrl"
  214. @inputFile="inputFile"
  215. bucket_name="cloud-coach"
  216. />
  217. </el-form-item>
  218. <el-form-item
  219. prop="sysMusicScore.speed"
  220. label="速度"
  221. :rules="[{ required: true, message: '请输入速度' }]"
  222. >
  223. <el-input
  224. placeholder="请输入速度"
  225. v-model="form.sysMusicScore.speed"
  226. />
  227. </el-form-item>
  228. <el-form-item
  229. v-if="form.sysMusicScore.isOpenMetronome === 1"
  230. label="伴奏(不含节拍器)"
  231. prop="sysMusicScore.url"
  232. >
  233. <singe-file-upload
  234. tips="仅支持上传 mp3/aac 格式音频文件"
  235. accept=".mp3, .aac"
  236. v-model="form.sysMusicScore.url"
  237. bucket_name="cloud-coach"
  238. />
  239. </el-form-item>
  240. <el-form-item
  241. v-else
  242. label="伴奏(含节拍器)"
  243. prop="sysMusicScore.metronomeUrl"
  244. >
  245. <singe-file-upload
  246. tips="仅支持上传 mp3/aac 格式音频文件"
  247. accept=".mp3, .aac"
  248. v-model="form.sysMusicScore.metronomeUrl"
  249. bucket_name="cloud-coach"
  250. />
  251. </el-form-item>
  252. <el-form-item label="MIDI" prop="sysMusicScore.midiUrl">
  253. <singe-file-upload
  254. tips="仅支持上传 mid 格式音频文件"
  255. accept=".mid"
  256. v-model="form.sysMusicScore.midiUrl"
  257. bucket_name="cloud-coach"
  258. />
  259. </el-form-item>
  260. <div v-if="gradual && gradual.length">
  261. <el-alert :closable="false" style="margin-bottom: 20px"
  262. >识别到共{{
  263. gradual.length
  264. }}处渐变速度,请输入Dorico对应小节时间信息</el-alert
  265. >
  266. <div v-for="(item,index) in gradual" :key="index">
  267. <el-form-item
  268. :label="item[0].measureIndex + 2 + ' 小节'"
  269. :rules="[
  270. { required: true, message: '请输入合奏曲目时间' },
  271. {
  272. pattern: /^((\d{2}):?){2,3}$/,
  273. message: '请输入正确的曲目时间',
  274. trigger: 'blur',
  275. },
  276. ]"
  277. :prop="`graduals.${item[0].measureIndex}`"
  278. >
  279. <el-input
  280. placeholder="00:00:00"
  281. v-model="form.graduals[item[0].measureIndex]"
  282. ></el-input>
  283. </el-form-item>
  284. <el-form-item
  285. :label="item[1].measureIndex + 2 + ' 小节'"
  286. :rules="[
  287. { required: true, message: '请输入合奏曲目时间' },
  288. {
  289. pattern: /^((\d{2}):?){2,3}$/,
  290. message: '请输入正确的曲目时间',
  291. trigger: 'blur',
  292. },
  293. ]"
  294. :prop="`graduals.${item[1].measureIndex}`"
  295. >
  296. <el-input
  297. placeholder="00:00:00"
  298. v-model="form.graduals[item[1].measureIndex]"
  299. ></el-input>
  300. </el-form-item>
  301. </div>
  302. </div>
  303. <div
  304. class="files"
  305. v-for="(song, index) in form.sysMusicScoreAccompaniments"
  306. :key="index"
  307. >
  308. <el-row>
  309. <!-- <el-col :span="12">
  310. </el-col> -->
  311. <!-- <el-col :span="12">
  312. </el-col> -->
  313. <el-col :span="12" v-if="partListNames.length > 1">
  314. <el-form-item
  315. label="所属轨道"
  316. :prop="`sysMusicScoreAccompaniments.${index}.track`"
  317. :rules="[{ required: true, message: '请选择所属轨道' }]"
  318. >
  319. <el-select
  320. style="width: 100% !important"
  321. v-model="song.track"
  322. clearable
  323. placeholder="请选择轨道"
  324. >
  325. <el-option
  326. v-for="item in partListNames"
  327. :value="item"
  328. :label="item"
  329. :key="item"
  330. :disabled="hasPartName(item)"
  331. ></el-option>
  332. </el-select>
  333. </el-form-item>
  334. </el-col>
  335. <el-col :span="12" v-if="form.sysMusicScore.isOpenMetronome === 1">
  336. <el-form-item
  337. label="原音(不含节拍器)"
  338. :prop="`sysMusicScoreAccompaniments.${index}.mp3Url`"
  339. :rules="[
  340. {
  341. required: form.sysMusicScore.isOpenMetronome
  342. ? form.sysMusicScore.playMode === 'MP3'
  343. : false,
  344. message: '请上传原音(不含节拍器)',
  345. },
  346. ]"
  347. >
  348. <singe-file-upload
  349. tips="仅支持上传 mp3/aac 格式音频文件"
  350. accept=".mp3, .aac"
  351. v-model="song.mp3Url"
  352. bucket_name="cloud-coach"
  353. />
  354. </el-form-item>
  355. </el-col>
  356. <el-col :span="12" v-else>
  357. <el-form-item
  358. label="原音(含节拍器)"
  359. :prop="`sysMusicScoreAccompaniments.${index}.metronomeMp3Url`"
  360. :rules="[
  361. {
  362. required: form.sysMusicScore.isOpenMetronome
  363. ? false
  364. : form.sysMusicScore.playMode === 'MP3',
  365. message: '原音(含节拍器)',
  366. },
  367. ]"
  368. >
  369. <singe-file-upload
  370. tips="仅支持上传 mp3/aac 格式音频文件"
  371. accept=".mp3, .aac"
  372. v-model="song.metronomeMp3Url"
  373. bucket_name="cloud-coach"
  374. />
  375. </el-form-item>
  376. </el-col>
  377. </el-row>
  378. <el-row>
  379. <el-col :span="24">
  380. <el-form-item
  381. :prop="`sysMusicScoreAccompaniments.${index}.memo`"
  382. label="描述"
  383. >
  384. <el-input
  385. type="textarea"
  386. :rows="2"
  387. placeholder="请输入描述"
  388. v-model="song.memo"
  389. />
  390. </el-form-item>
  391. </el-col>
  392. <!-- <el-col :span="12">
  393. </el-col> -->
  394. </el-row>
  395. <el-button
  396. class="file-remove"
  397. type="text"
  398. @click="removeSys(index)"
  399. :disabled="form.sysMusicScoreAccompaniments.length == 1"
  400. >删除</el-button
  401. >
  402. </div>
  403. <el-button
  404. @click="createSys"
  405. type="info"
  406. style="width: 100%; margin-bottom: 20px"
  407. plain
  408. >添加原音</el-button
  409. >
  410. <div class="btns">
  411. <el-button type="primary" @click="submit">提交</el-button>
  412. <el-button @click="$listeners.close">取消</el-button>
  413. </div>
  414. </el-form>
  415. </div>
  416. </template>
  417. <script>
  418. import axios from "axios";
  419. import { Add, Update, queryPageSysExam, queryTree } from "../api";
  420. import { getAllmemberRank } from "@/views/resetTeaming/api";
  421. const initailExtConfigJson = {
  422. repeatedBeats: 0,
  423. };
  424. export default {
  425. props: ["detail", "type"],
  426. data() {
  427. return {
  428. gradual: null,
  429. xmlFirstSpeed: "",
  430. partListNames: [],
  431. tree: [],
  432. extConfigJson: {},
  433. memberRankList: [], // 会员列表
  434. form: {
  435. graduals: {},
  436. rankIdType: 0, // 收费会员类型 默认免费
  437. repeatedBeats: 0, // 重复节拍
  438. sysMusicScore: {
  439. isOpenMetronome: 0, // 是否开启节拍器 默认关闭
  440. name: "",
  441. rankIds: "", // 收费会员编号
  442. url: "",
  443. metronomeUrl: "",
  444. midiUrl: "",
  445. order: "",
  446. musicScoreCategoriesId: [],
  447. // 兼容之前数据,默认选择云教练
  448. clientType: "SMART_PRACTICE",
  449. renderFrom: "",
  450. playMode: "MP3",
  451. enableEvaluation: 1,
  452. extConfigJson: "{}",
  453. subjectId:null,
  454. speed:'',
  455. xmlUrl:null,
  456. isShowFingering:null,
  457. },
  458. sysMusicScoreAccompaniments: [
  459. {
  460. subjectId: "",
  461. speed: "",
  462. mp3Url: "",
  463. xmlUrl: "",
  464. isShowFingering: true,
  465. memo: "",
  466. track: "",
  467. },
  468. ],
  469. delExamSongAccompanimentIds: [],
  470. },
  471. treeProps: {
  472. value: "id",
  473. label: "name",
  474. children: "sysMusicScoreCategoriesList",
  475. },
  476. };
  477. },
  478. async mounted() {
  479. this.$store.dispatch("setSubjects");
  480. await this.FetchTree();
  481. await this.memberRank();
  482. if (this.detail) {
  483. try {
  484. this.extConfigJson = JSON.parse(this.detail.extConfigJson);
  485. } catch (error) {
  486. this.extConfigJson = { ...initailExtConfigJson };
  487. }
  488. this.form.repeatedBeats = this.extConfigJson.repeatedBeats;
  489. this.form.graduals = this.extConfigJson.gradualTimes || {};
  490. this.$set(this.form, "sysMusicScore", {
  491. isOpenMetronome: Number(this.detail.isOpenMetronome),
  492. name: this.detail.name,
  493. url: this.detail.url,
  494. midiUrl: this.detail.midiUrl,
  495. rankIds: this.detail.rankIds,
  496. order: this.detail.order,
  497. clientType: this.detail.clientType,
  498. enableEvaluation: +this.detail.enableEvaluation,
  499. metronomeUrl: this.detail.metronomeUrl,
  500. renderFrom: this.detail.renderFrom,
  501. playMode: this.detail.playMode,
  502. musicScoreCategoriesId: this.detail.categoriesId
  503. ? this.formatParentId(this.detail.categoriesId, this.tree)
  504. : [],
  505. });
  506. if (this.detail.rankIds) {
  507. this.form.rankIdType = 1;
  508. } else {
  509. this.form.rankIdType = 0;
  510. }
  511. const xmlres = await axios(this.detail.xmlUrl);
  512. this.gradual = getGradualLengthByXml(xmlres.data).filter(
  513. (item) => item.length === 2
  514. );
  515. this.partListNames = this.getPartListNames(xmlres.data);
  516. this.FeatchDetailList();
  517. } else {
  518. // 新增条件下默认设置为收费
  519. this.rankChange(1);
  520. this.form.rankIdType = 1;
  521. }
  522. },
  523. methods: {
  524. getPartListNames(xml) {
  525. if (!xml) return [];
  526. const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
  527. const partList =
  528. xmlParse
  529. .getElementsByTagName("part-list")?.[0]
  530. ?.getElementsByTagName("score-part") || [];
  531. const partListNames = Array.from(partList).map(
  532. (item) => item.getElementsByTagName("part-name")?.[0].textContent || ""
  533. );
  534. this.xmlFirstSpeed =
  535. xmlParse.getElementsByTagName("per-minute")?.[0]?.textContent || "";
  536. // this.form.sysMusicScore.speed = this.xmlFirstSpeed;
  537. this.$set(this.form.sysMusicScore,'speed',this.xmlFirstSpeed)
  538. return partListNames.filter(
  539. (text) => text.toLocaleUpperCase() !== "COMMON"
  540. );
  541. },
  542. inputFile(file) {
  543. const xmlRead = new FileReader();
  544. xmlRead.onload = (res) => {
  545. this.partListNames = this.getPartListNames(res.target.result);
  546. this.gradual = getGradualLengthByXml(res.target.result).filter(
  547. (item) => item.length === 2
  548. );
  549. for (let j = 0; j < this.form.sysMusicScoreAccompaniments.length; j++) {
  550. this.form.sysMusicScoreAccompaniments[j].track =
  551. this.partListNames[j];
  552. if (!this.form.sysMusicScoreAccompaniments[j].speed) {
  553. this.form.sysMusicScoreAccompaniments[j].speed = this.xmlFirstSpeed;
  554. }
  555. this.$set(
  556. this.form,
  557. "sysMusicScoreAccompaniments",
  558. this.form.sysMusicScoreAccompaniments
  559. );
  560. }
  561. for (
  562. let index = this.form.sysMusicScoreAccompaniments.length;
  563. index < this.partListNames.length;
  564. index++
  565. ) {
  566. const part = this.partListNames[index];
  567. const sysData = {
  568. ...this.form.sysMusicScoreAccompaniments[0],
  569. metronomeMp3Url: "",
  570. mp3Url: "",
  571. memo:'',
  572. track: part,
  573. };
  574. if (!sysData.speed) {
  575. sysData.speed = this.xmlFirstSpeed;
  576. }
  577. this.createSys(sysData);
  578. }
  579. };
  580. xmlRead.readAsText(file.raw);
  581. },
  582. rankChange(value) {
  583. if (value) {
  584. let tempIds = [];
  585. this.memberRankList.forEach((item) => {
  586. tempIds.push(item.id);
  587. });
  588. this.form.sysMusicScore.rankIds = tempIds.join(",");
  589. } else {
  590. // 会员购买重置
  591. this.form.sysMusicScore.rankIds = "";
  592. }
  593. },
  594. async memberRank() {
  595. try {
  596. const res = await getAllmemberRank({ isDefault: 0 });
  597. this.memberRankList = res.data || [];
  598. } catch (e) {
  599. console.log(e);
  600. }
  601. },
  602. formatParentId(id, list, ids = []) {
  603. for (const item of list) {
  604. if (item.sysMusicScoreCategoriesList) {
  605. const cIds = this.formatParentId(
  606. id,
  607. item.sysMusicScoreCategoriesList,
  608. [...ids, item.id]
  609. );
  610. if (cIds.includes(id)) {
  611. return cIds;
  612. }
  613. }
  614. if (item.id === id) {
  615. return [...ids, id];
  616. }
  617. }
  618. return ids;
  619. },
  620. async FetchTree() {
  621. try {
  622. const res = await queryTree();
  623. this.tree = res.data;
  624. this.formatTree(this.tree);
  625. } catch (error) {}
  626. },
  627. formatTree(data) {
  628. for (let i of data) {
  629. if (
  630. i.sysMusicScoreCategoriesList &&
  631. i.sysMusicScoreCategoriesList.length > 0
  632. ) {
  633. this.formatTree(i.sysMusicScoreCategoriesList, i);
  634. } else {
  635. i.sysMusicScoreCategoriesList = null;
  636. }
  637. }
  638. },
  639. async FeatchDetailList() {
  640. try {
  641. const res = await queryPageSysExam({
  642. sysMusicScoreId: this.detail.id,
  643. });
  644. const result = res.data || [];
  645. result.forEach((item) => {
  646. if (!item.subjectId) {
  647. item.subjectId = null;
  648. }
  649. });
  650. if(result.length >0){
  651. console.log(result[0].speed)
  652. this.$set(this.form.sysMusicScore, "subjectId", result[0].subjectId);
  653. this.$set(this.form.sysMusicScore, "speed", result[0].speed);
  654. this.$set(this.form.sysMusicScore, "xmlUrl", result[0].xmlUrl);
  655. this.$set(this.form.sysMusicScore, "isShowFingering", result[0].isShowFingering);
  656. }
  657. this.$set(this.form, "sysMusicScoreAccompaniments", result);
  658. } catch (error) {}
  659. },
  660. createSys(initData) {
  661. this.form.sysMusicScoreAccompaniments.push(
  662. Object.assign(
  663. {
  664. subjectId: "",
  665. speed: "",
  666. mp3Url: "",
  667. xmlUrl: "",
  668. track: "",
  669. },
  670. initData || {}
  671. )
  672. );
  673. },
  674. async removeSys(index) {
  675. try {
  676. await this.$confirm("是否确认删除此原音?", "提示", {
  677. type: "warning",
  678. });
  679. if (this.form.sysMusicScoreAccompaniments[index]) {
  680. this.form.delExamSongAccompanimentIds.push(
  681. this.form.sysMusicScoreAccompaniments[index].id
  682. );
  683. }
  684. this.form.sysMusicScoreAccompaniments.splice(index, 1);
  685. } catch (error) {}
  686. },
  687. hasPartName(name) {
  688. const names = [];
  689. for (const item of this.form.sysMusicScoreAccompaniments) {
  690. names.push(item.track);
  691. }
  692. return names.includes(name);
  693. },
  694. hasSubjectId(id) {
  695. const ids = [];
  696. for (const item of this.form.sysMusicScoreAccompaniments) {
  697. ids.push(item.subjectId);
  698. }
  699. return ids.includes(id);
  700. },
  701. async submit() {
  702. this.$refs.form.validate(async (valid) => {
  703. if (valid) {
  704. // 提交前平铺 速度,声部,XML,isShowFingering
  705. this.form.sysMusicScoreAccompaniments.forEach(item=>{
  706. item.speed = this.form.sysMusicScore.speed
  707. item.subjectId = this.form.sysMusicScore.subjectId
  708. item.xmlUrl = this.form.sysMusicScore.xmlUrl
  709. item.isShowFingering = this.form.sysMusicScore.isShowFingering
  710. })
  711. console.log(this.form)
  712. if (!this.detail) {
  713. await Add({
  714. ...this.form,
  715. sysMusicScore: {
  716. ...this.form.sysMusicScore,
  717. extConfigJson: JSON.stringify({
  718. repeatedBeats: this.form.repeatedBeats,
  719. gradualTimes: this.form.graduals,
  720. }),
  721. type: "COMMON",
  722. showFlag: 0,
  723. musicScoreCategoriesId: (
  724. this.form.sysMusicScore.musicScoreCategoriesId || []
  725. ).pop(),
  726. },
  727. });
  728. this.$message.success("提交成功");
  729. } else {
  730. await Update({
  731. ...this.form,
  732. sysMusicScore: {
  733. ...this.form.sysMusicScore,
  734. extConfigJson: JSON.stringify({
  735. repeatedBeats: this.form.repeatedBeats,
  736. gradualTimes: this.form.graduals,
  737. }),
  738. type: "COMMON",
  739. id: this.detail.id,
  740. showFlag: this.detail.showFlag,
  741. musicScoreCategoriesId: (
  742. this.form.sysMusicScore.musicScoreCategoriesId || []
  743. ).pop(),
  744. },
  745. });
  746. this.$message.success("修改成功");
  747. }
  748. this.$listeners.close();
  749. this.$listeners.submited();
  750. }
  751. });
  752. },
  753. },
  754. };
  755. /**
  756. * 获取指定元素下一个Note元素
  757. * @param ele 指定元素
  758. * @param selectors 选择器
  759. */
  760. const getNextNote = (ele, selectors) => {
  761. let index = 0;
  762. const parentEle = ele.closest(selectors);
  763. let pointer = parentEle;
  764. const measure = parentEle?.closest("measure");
  765. let siblingNote = null;
  766. // 查找到相邻的第一个note元素
  767. while (!siblingNote && index < (measure?.childNodes.length || 50)) {
  768. index++;
  769. if (pointer?.nextElementSibling?.tagName === "note") {
  770. siblingNote = pointer?.nextElementSibling;
  771. }
  772. pointer = pointer?.nextElementSibling;
  773. }
  774. return siblingNote;
  775. };
  776. export const onlyVisible = (xml, partIndex) => {
  777. if (!xml) return "";
  778. const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
  779. const partList =
  780. xmlParse
  781. .getElementsByTagName("part-list")?.[0]
  782. ?.getElementsByTagName("score-part") || [];
  783. // const partListNames = Array.from(partList).map(item => item.getElementsByTagName('part-name')?.[0].textContent || '')
  784. const parts = xmlParse.getElementsByTagName("part");
  785. // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
  786. // const firstMeasures = [...Array.from(parts[0]?.getElementsByTagName('measure') || [])]
  787. // const metronomes = [...Array.from(parts[0]?.getElementsByTagName('metronome') || [])]
  788. // const words = [...Array.from(parts[0]?.getElementsByTagName('words') || [])]
  789. // const codas = [...Array.from(parts[0]?.getElementsByTagName('coda') || [])]
  790. // const rehearsals = [...Array.from(parts[0]?.getElementsByTagName('rehearsal') || [])]
  791. const visiblePartInfo = partList[partIndex];
  792. // console.log(visiblePartInfo, partIndex)
  793. // state.partListNames = partListNames
  794. if (visiblePartInfo) {
  795. const id = visiblePartInfo.getAttribute("id");
  796. Array.from(parts).forEach((part) => {
  797. if (part && part.getAttribute("id") !== id) {
  798. part.parentNode?.removeChild(part);
  799. // 不等于第一行才添加避免重复添加
  800. } else {
  801. // words.forEach(word => {
  802. // const text = word.textContent || ''
  803. // if(isSpeedKeyword(text) && text) {
  804. // const wordContainer = word.parentElement?.parentElement?.parentElement
  805. // if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {
  806. // const wordParent = word.parentElement?.parentElement
  807. // const fisrt = wordContainer.firstElementChild
  808. // wordContainer.insertBefore(wordParent, fisrt)
  809. // }
  810. // }
  811. // })
  812. }
  813. // 最后一个小节的结束线元素不在最后 调整
  814. if (part && part.getAttribute("id") === id) {
  815. const barlines = part.getElementsByTagName("barline");
  816. const lastParent = barlines[barlines.length - 1]?.parentElement;
  817. if (lastParent?.lastElementChild?.tagName !== "barline") {
  818. const children = lastParent?.children || [];
  819. for (let el of children) {
  820. if (el.tagName === "barline") {
  821. // 将结束线元素放到最后
  822. lastParent?.appendChild(el);
  823. break;
  824. }
  825. }
  826. }
  827. }
  828. });
  829. Array.from(partList).forEach((part) => {
  830. if (part && part.getAttribute("id") !== id) {
  831. part.parentNode?.removeChild(part);
  832. }
  833. });
  834. // 处理装饰音问题
  835. const notes = xmlParse.getElementsByTagName("note");
  836. const getNextvNoteDuration = (i) => {
  837. let nextNote = notes[i + 1];
  838. // 可能存在多个装饰音问题,取下一个非装饰音时值
  839. for (let index = i; index < notes.length; index++) {
  840. const note = notes[index];
  841. if (!note.getElementsByTagName("grace")?.length) {
  842. nextNote = note;
  843. break;
  844. }
  845. }
  846. const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0];
  847. return nextNoteDuration;
  848. };
  849. Array.from(notes).forEach((note, i) => {
  850. const graces = note.getElementsByTagName("grace");
  851. if (graces && graces.length) {
  852. // if (i !== 0) {
  853. note.appendChild(getNextvNoteDuration(i)?.cloneNode(true));
  854. // }
  855. }
  856. });
  857. }
  858. // console.log(new XMLSerializer().serializeToString(xmlParse))
  859. return new XMLSerializer().serializeToString(xmlParse);
  860. };
  861. const speedInfo = {
  862. "rall.": 1.333333333,
  863. "poco rit.": 1.333333333,
  864. "rit.": 1.333333333,
  865. "molto rit.": 1.333333333,
  866. "molto rall": 1.333333333,
  867. lentando: 1.333333333,
  868. allargando: 1.333333333,
  869. morendo: 1.333333333,
  870. "accel.": 0.8,
  871. calando: 2,
  872. "poco accel.": 0.8,
  873. };
  874. /**
  875. * 按照xml进行减慢速度的计算
  876. * @param xml 始终按照第一分谱进行减慢速度的计算
  877. */
  878. export function getGradualLengthByXml(xml) {
  879. const firstPartXml = onlyVisible(xml, 0);
  880. const xmlParse = new DOMParser().parseFromString(firstPartXml, "text/xml");
  881. const measures = Array.from(xmlParse.querySelectorAll("measure"));
  882. const notes = Array.from(xmlParse.querySelectorAll("note"));
  883. const words = Array.from(xmlParse.querySelectorAll("words"));
  884. const metronomes = Array.from(xmlParse.querySelectorAll("metronome"));
  885. const eles = [];
  886. for (const ele of [...words, ...metronomes]) {
  887. const note = getNextNote(ele, "direction");
  888. // console.log(ele, note)
  889. if (note) {
  890. const measure = note?.closest("measure");
  891. const measureNotes = Array.from(measure.querySelectorAll("note"));
  892. const noteInMeasureIndex = Array.from(measure.childNodes)
  893. .filter((item) => item.nodeName === "note")
  894. .findIndex((item) => item === note);
  895. let allDuration = 0;
  896. let leftDuration = 0;
  897. for (let i = 0; i < measureNotes.length; i++) {
  898. const n = measureNotes[i];
  899. const duration = +(n.querySelector("duration")?.textContent || "0");
  900. allDuration += duration;
  901. if (i < noteInMeasureIndex) {
  902. leftDuration = allDuration;
  903. }
  904. }
  905. eles.push({
  906. ele,
  907. index: notes.indexOf(note),
  908. noteInMeasureIndex,
  909. textContent: ele.textContent,
  910. measureIndex: measures.indexOf(measure),
  911. type: ele.tagName,
  912. allDuration,
  913. leftDuration,
  914. });
  915. }
  916. }
  917. const gradualNotes = [];
  918. eles.sort((a, b) => a.index - b.index);
  919. const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase());
  920. for (const ele of eles) {
  921. const textContent = ele.textContent?.toLocaleLowerCase().trim();
  922. if (ele.type === "words" && keys.includes(textContent)) {
  923. gradualNotes.push([
  924. {
  925. start: ele.index,
  926. measureIndex: ele.measureIndex,
  927. noteInMeasureIndex: ele.noteInMeasureIndex,
  928. allDuration: ele.allDuration,
  929. leftDuration: ele.leftDuration,
  930. type: textContent,
  931. },
  932. ]);
  933. }
  934. if (
  935. ele.type === "metronome" ||
  936. (ele.type === "words" && textContent === "a tempo")
  937. ) {
  938. const indexOf = gradualNotes.findIndex((item) => item.length === 1);
  939. if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
  940. gradualNotes[indexOf][1] = {
  941. start: ele.index,
  942. measureIndex: ele.measureIndex,
  943. noteInMeasureIndex: ele.noteInMeasureIndex,
  944. allDuration: ele.allDuration,
  945. leftDuration: ele.leftDuration,
  946. type: textContent,
  947. };
  948. }
  949. }
  950. }
  951. return gradualNotes;
  952. }
  953. </script>
  954. <style lang="less" scoped>
  955. .btns {
  956. text-align: right;
  957. }
  958. .files {
  959. background-color: #f8f8f8;
  960. padding: 20px 0;
  961. padding-right: 20px;
  962. margin-bottom: 20px;
  963. border-radius: 5px;
  964. position: relative;
  965. .file-remove {
  966. position: absolute;
  967. right: 20px;
  968. bottom: 10px;
  969. }
  970. }
  971. </style>