cloudPractice.tsx 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359
  1. import { computed, defineComponent, nextTick, onMounted, reactive, ref, shallowRef, watch } from "vue"
  2. import styles from "./index.module.scss"
  3. import NavContainer from "@/businessComponents/navContainer"
  4. import { ElEmpty, ElMessage, ElScrollbar } from "element-plus"
  5. import Dictionary from "@/components/dictionary"
  6. import MyInput from "@/components/myInput"
  7. import { NImage, NPopselect, NSpin, NTooltip } from "naive-ui"
  8. import PlayItem from "./component/play-item"
  9. import icon_default from "../../img/cloudPractice/icon_default.png"
  10. import iconBtnPause from "../../img/cloudPractice/icon-btn-pause.png"
  11. import iconBtnPlay from "../../img/cloudPractice/icon-btn-play.png"
  12. import btnSubmit from "../../img/cloudPractice/btn-submit.png"
  13. import iconTransfer from "../../img/cloudPractice/icon-transfer.png"
  14. import iconDownload from "../../img/cloudPractice/icon-download.png"
  15. import { httpAjaxErrMsg } from "@/plugin/httpAjax"
  16. import {
  17. queryPage2_gym,
  18. queryPage2_gyt,
  19. queryPage2_klx,
  20. querySubjectIds_gym,
  21. querySubjectIds_gyt,
  22. querySubjectIds_klx,
  23. queryTree_gym,
  24. queryTree_gyt,
  25. queryTree_klx,
  26. selectCondition_klx
  27. } from "@/api/cloudPractice.api"
  28. import axios from "axios"
  29. import { getInstrumentName } from "@/libs/instruments"
  30. import { formatXML, getCustomInfo, onlyMutVisible, onlyVisible } from "./instrument"
  31. import { useFunction } from "./useData"
  32. import userStore from "@/store/modules/user"
  33. import PlayLoading from "./component/play-loading"
  34. import PracticeForm from "@/businessComponents/practiceForm"
  35. import { saveAs } from "file-saver"
  36. import JSZip from "jszip"
  37. import { svgtoblob } from "./formatSvgToImg"
  38. import { penShow, whitePenShow } from "@/businessComponents/globalTools/globalTools"
  39. export default defineComponent({
  40. name: "cloudPractice",
  41. setup() {
  42. const userStoreHook = userStore()
  43. const { goToCloud, getPreViewCloud, isPracticeShow, practiceUrl, handlePracticeClose } = useFunction()
  44. const navs = [
  45. {
  46. name: "主页",
  47. url: "/"
  48. },
  49. {
  50. name: "云练习"
  51. }
  52. ]
  53. const spinRef = ref()
  54. const state = reactive({
  55. finshed: false,
  56. reshing: false,
  57. page: 1,
  58. rows: 20,
  59. iframeSrc: "",
  60. listActive: 0, // 当前选中的对象
  61. firstTreeId: null as any, // 左侧
  62. categoryId: null as any, // 类型
  63. categoryName: "" as any, // 类型名称
  64. categoryList: [] as any[],
  65. levelList: [] as any[], // 级别
  66. levelId: null as any,
  67. typeList: [] as any[], // 类型
  68. typeId: -1 as any,
  69. subjectList: [] as any[], // 声部列表
  70. subjectId: -1 as any,
  71. list: [] as any[],
  72. searchStatus: false,
  73. queryStr: "", // 搜索条件
  74. partList: [] as any[],
  75. partNames: [] as any[],
  76. selectedPartName: "" as any,
  77. selectedTrack: "" as any,
  78. selectedPartIndex: 0,
  79. partXmlIndex: 0,
  80. musicPdfUrl: "", // 五线谱文件
  81. imgs: [] as any[], // 图片列表
  82. categoryShow: false, // 是否展开
  83. playState: "pause" as "play" | "pause", // 播放状态
  84. showPlayer: false // 是否显示播放器
  85. })
  86. const partColumns = ref<any>([])
  87. /** 选中的item */
  88. const activeItem = computed(() => {
  89. const list = state.list[state.listActive] || {}
  90. let tempList: any = {}
  91. if (userStoreHook.roles === "GYM") {
  92. const item = list.background?.[0]
  93. const audioFileUrl = item?.musicSheetType === "CONCERT" ? item?.metronomeUrl : item?.metronomeMp3Url || item?.mp3Url
  94. tempList = {
  95. id: item?.id,
  96. name: item?.examSongName,
  97. background: list?.background,
  98. xmlUrl: item?.xmlUrl,
  99. musicSheetType: item?.musicSheetType,
  100. audioFileUrl,
  101. // titleImg: list?.titleImg,
  102. isScoreRender: item?.isScoreRender,
  103. defaultScoreRender: item?.defaultScoreRender,
  104. musicPdfUrl: item?.musicPdfUrl // 独奏使用PDF
  105. }
  106. } else if (userStoreHook.roles === "GYT") {
  107. tempList = {
  108. id: list?.id,
  109. name: list?.musicSheetName,
  110. background: list?.background,
  111. xmlUrl: list?.xmlFileUrl,
  112. musicSheetType: list?.musicSheetType,
  113. audioFileUrl: list?.audioFileUrl,
  114. titleImg: list?.titleImg,
  115. isScoreRender: list?.isScoreRender,
  116. defaultScoreRender: list?.defaultScoreRender,
  117. musicPdfUrl: list?.musicPdfUrl
  118. }
  119. } else if (userStoreHook.roles === "KLX") {
  120. const item: any = list.background?.[0]
  121. tempList = {
  122. id: list?.id,
  123. name: list?.musicSheetName,
  124. background: list?.background,
  125. xmlUrl: list?.xmlFileUrl,
  126. musicSheetType: list?.musicSheetType,
  127. audioFileUrl: item?.audioFileUrl,
  128. titleImg: list?.titleImg,
  129. isScoreRender: false,
  130. defaultScoreRender: false,
  131. musicPdfUrl: list?.musicPdfUrl
  132. }
  133. }
  134. return tempList
  135. })
  136. const songPrevNextStatus = computed(() => {
  137. let prev = true,
  138. next = true
  139. if (state.listActive === 0) {
  140. prev = false
  141. }
  142. if (state.listActive >= state.list.length - 1) {
  143. next = false
  144. }
  145. return {
  146. prev,
  147. next
  148. }
  149. })
  150. const downloadStatus = ref(false)
  151. const loading = ref(false)
  152. const staffLoading = ref(false)
  153. const storeData = shallowRef<any[]>([])
  154. const handleSearchList_gym = async () => {
  155. loading.value = true
  156. await httpAjaxErrMsg(queryTree_gym).then(async res => {
  157. loading.value = false
  158. if (res.code === 200) {
  159. storeData.value = res.data || []
  160. await setDefaultData()
  161. }
  162. })
  163. }
  164. const handleGetSubject_gym = async () => {
  165. loading.value = true
  166. await httpAjaxErrMsg(querySubjectIds_gym, { categoriesId: state.categoryId || state.firstTreeId }).then(res => {
  167. loading.value = false
  168. if (res.code === 200) {
  169. const result = res.data || []
  170. state.subjectList = result.map((item: any) => {
  171. return {
  172. label: item.name,
  173. value: item.id
  174. }
  175. })
  176. state.subjectList.unshift({
  177. label: "全部声部",
  178. value: -1
  179. })
  180. const userSubjectId = userStoreHook.userInfo.subjectId
  181. if (userSubjectId) {
  182. const tempSubjectId = userSubjectId.split(",")[0]
  183. state.subjectList.forEach((item: any) => {
  184. // 判断是否存在声部编号
  185. if (item.value === Number(tempSubjectId)) {
  186. state.subjectId = Number(tempSubjectId)
  187. }
  188. })
  189. }
  190. }
  191. })
  192. }
  193. const handleGetList_gym = async () => {
  194. loading.value = true
  195. const params = {
  196. page: state.page,
  197. rows: state.rows,
  198. subjectId: state.subjectId === -1 ? null : state.subjectId,
  199. categoriesId: state.typeId === -1 ? state.levelId : state.typeId,
  200. search: state.queryStr
  201. }
  202. await httpAjaxErrMsg(queryPage2_gym, params).then(res => {
  203. loading.value = false
  204. if (res.code === 200) {
  205. const result = res.data || []
  206. if (state.reshing) {
  207. state.list = []
  208. state.reshing = false
  209. }
  210. if (Array.isArray(result.rows)) {
  211. state.list = [...state.list, ...result.rows]
  212. state.finshed = state.page >= result.totalPage
  213. } else {
  214. state.finshed = true
  215. }
  216. } else {
  217. state.finshed = true
  218. }
  219. })
  220. }
  221. /** 管乐团数据查询 */
  222. const handleSearchList_gyt = async () => {
  223. loading.value = true
  224. await httpAjaxErrMsg(queryTree_gyt, {
  225. enable: true,
  226. page: 1,
  227. parentId: 0,
  228. rows: 10
  229. }).then(res => {
  230. loading.value = false
  231. if (res.code === 200) {
  232. storeData.value = res.data || []
  233. setDefaultData()
  234. }
  235. })
  236. }
  237. const handleGetSubject_gyt = async () => {
  238. loading.value = true
  239. await httpAjaxErrMsg(querySubjectIds_gyt, {
  240. enableFlag: true,
  241. page: 1,
  242. rows: 100
  243. }).then(res => {
  244. loading.value = false
  245. if (res.code === 200) {
  246. const result = res.data || []
  247. state.subjectList = result.map((item: any) => {
  248. return {
  249. label: item.name,
  250. value: item.id
  251. }
  252. })
  253. state.subjectList.unshift({
  254. label: "全部声部",
  255. value: -1
  256. })
  257. const userSubjectId = userStoreHook.userInfo.subjectId
  258. if (userSubjectId) {
  259. const tempSubjectId = userSubjectId.split(",")[0]
  260. state.subjectList.forEach((item: any) => {
  261. // 判断是否存在声部编号
  262. if (item.value === Number(tempSubjectId)) {
  263. state.subjectId = Number(tempSubjectId)
  264. }
  265. })
  266. }
  267. }
  268. })
  269. }
  270. const handleGetList_gyt = async () => {
  271. loading.value = true
  272. const params = {
  273. page: state.page,
  274. rows: state.rows,
  275. musicSubject: state.subjectId === -1 ? null : state.subjectId,
  276. musicSheetCategoriesId: state.typeId === -1 ? state.levelId : state.typeId,
  277. keyword: state.queryStr,
  278. detailFlag: true,
  279. status: 1
  280. }
  281. await httpAjaxErrMsg(queryPage2_gyt, params).then(res => {
  282. loading.value = false
  283. if (res.code === 200) {
  284. const result = res.data || {}
  285. if (state.reshing) {
  286. state.list = []
  287. state.reshing = false
  288. }
  289. if (Array.isArray(result.rows)) {
  290. result.rows.forEach((item: any) => {
  291. item.name = item.musicSheetName
  292. })
  293. state.list = [...state.list, ...result.rows]
  294. state.finshed = state.page >= result.pages
  295. } else {
  296. state.finshed = true
  297. }
  298. } else {
  299. state.finshed = true
  300. }
  301. })
  302. }
  303. /** 酷乐秀机构数据查询 */
  304. const handleSearchList_klx = async () => {
  305. loading.value = true
  306. await httpAjaxErrMsg(queryTree_klx, {
  307. enable: true,
  308. page: 1,
  309. parentId: 0,
  310. rows: 10
  311. }).then(async res => {
  312. loading.value = false
  313. if (res.code === 200) {
  314. const result = res.data || []
  315. const tempList: any = []
  316. result.forEach((item: any) => {
  317. if (item.musicNum > 0) {
  318. const subjectCounts = item.subjectCounts ? true : false
  319. const musicCounts = item.musicCounts ? true : false
  320. const ensembleCounts = item.ensembleCounts ? true : false
  321. const list: any = []
  322. if (subjectCounts) {
  323. list.push({
  324. label: "基础云练",
  325. value: "SUBJECT"
  326. })
  327. }
  328. if (musicCounts) {
  329. list.push({
  330. label: "独奏云练",
  331. value: "MUSIC"
  332. })
  333. }
  334. if (ensembleCounts) {
  335. list.push({
  336. label: "合奏云练",
  337. value: "ENSEMBLE"
  338. })
  339. }
  340. tempList.push({
  341. value: item.id,
  342. label: item.name,
  343. musicSheetCategoriesList: list
  344. })
  345. }
  346. })
  347. state.categoryList = tempList
  348. await setDefaultData()
  349. } else {
  350. state.finshed = true
  351. }
  352. })
  353. }
  354. const handleGetSubject_klx = async () => {
  355. loading.value = true
  356. await httpAjaxErrMsg(querySubjectIds_klx, {
  357. queryType: "list",
  358. page: 1,
  359. rows: 100
  360. }).then(res => {
  361. loading.value = false
  362. if (res.code === 200) {
  363. const result = res.data?.rows || []
  364. state.subjectList = result.map((item: any) => {
  365. return {
  366. label: item.name,
  367. value: item.id
  368. }
  369. })
  370. state.subjectList.unshift({
  371. label: "全部声部",
  372. value: -1
  373. })
  374. const userSubjectId = userStoreHook.userInfo.subjectId
  375. if (userSubjectId) {
  376. const tempSubjectId = userSubjectId.split(",")[0]
  377. state.subjectList.forEach((item: any) => {
  378. // 判断是否存在声部编号
  379. if (item.value === Number(tempSubjectId)) {
  380. state.subjectId = Number(tempSubjectId)
  381. }
  382. })
  383. }
  384. }
  385. })
  386. }
  387. const handleGetList_klx = async () => {
  388. if (!state.categoryId) return
  389. loading.value = true
  390. const params = {
  391. page: state.page,
  392. rows: state.rows,
  393. albumId: state.categoryId,
  394. subjectId: state.subjectId === -1 ? null : state.subjectId,
  395. subjectType: state.firstTreeId,
  396. level: state.levelId === -1 ? null : state.levelId,
  397. type: state.typeId === -1 ? null : state.typeId,
  398. keyword: state.queryStr
  399. }
  400. await httpAjaxErrMsg(queryPage2_klx, params).then(res => {
  401. loading.value = false
  402. if (res.code === 200) {
  403. const result = res.data || {}
  404. if (state.reshing) {
  405. state.list = []
  406. state.reshing = false
  407. }
  408. if (Array.isArray(result.rows)) {
  409. result.rows.forEach((item: any) => {
  410. item.name = item.musicSheetName
  411. })
  412. state.list = [...state.list, ...result.rows]
  413. state.finshed = state.page >= result.totalPage
  414. } else {
  415. state.finshed = true
  416. }
  417. } else {
  418. state.finshed = true
  419. }
  420. })
  421. }
  422. const handleSelectCondition_klx = async () => {
  423. if (!state.categoryId || !state.firstTreeId) return
  424. loading.value = true
  425. const params = {
  426. tenantAlbumId: state.categoryId,
  427. subjectType: state.firstTreeId
  428. }
  429. await httpAjaxErrMsg(selectCondition_klx, params).then(res => {
  430. loading.value = false
  431. if (res.code === 200) {
  432. const result = res.data || {}
  433. if (result.levelList && result.levelList.length > 0) {
  434. state.levelList = result.levelList.map((item: any) => {
  435. return {
  436. label: item.value,
  437. value: item.id
  438. }
  439. })
  440. state.levelList.unshift({
  441. label: "全部级别",
  442. value: -1
  443. })
  444. state.levelId = -1
  445. } else {
  446. state.levelList = []
  447. }
  448. if (result.typeList && result.typeList.length > 0) {
  449. state.typeList = result.typeList.map((item: any) => {
  450. return {
  451. label: item.value,
  452. value: item.id
  453. }
  454. })
  455. state.typeList.unshift({
  456. label: "全部类型",
  457. value: -1
  458. })
  459. state.typeId = -1
  460. } else {
  461. state.typeList = []
  462. }
  463. }
  464. })
  465. }
  466. /** 条件查询 */
  467. const handleAllSearchList = async () => {
  468. // GYM,GYT,KLX 区分 查询搜索条件数据
  469. if (userStoreHook.roles === "GYM") {
  470. await handleSearchList_gym()
  471. } else if (userStoreHook.roles === "GYT") {
  472. await handleSearchList_gyt()
  473. } else if (userStoreHook.roles === "KLX") {
  474. await handleSearchList_klx()
  475. }
  476. }
  477. const handleAllGetSubject = async () => {
  478. // GYM,GYT,KLX 区分 查询声部数据
  479. if (userStoreHook.roles === "GYM") {
  480. await handleGetSubject_gym()
  481. } else if (userStoreHook.roles === "GYT") {
  482. await handleGetSubject_gyt()
  483. } else if (userStoreHook.roles === "KLX") {
  484. await handleGetSubject_klx()
  485. }
  486. }
  487. const handleAllGetList = async () => {
  488. // GYM,GYT,KLX 区分 查询声部数据·
  489. if (userStoreHook.roles === "GYM") {
  490. await handleGetList_gym()
  491. } else if (userStoreHook.roles === "GYT") {
  492. await handleGetList_gyt()
  493. } else if (userStoreHook.roles === "KLX") {
  494. await handleGetList_klx()
  495. }
  496. }
  497. /** 初始化数据 */
  498. const setDefaultData = async (type?: "first" | "category" | "level" | "type") => {
  499. if (userStoreHook.roles === "GYM") {
  500. await initCategories_gym(type)
  501. } else if (userStoreHook.roles === "GYT") {
  502. initCategories_gyt(type)
  503. } else if (userStoreHook.roles === "KLX") {
  504. await initCategories_klx(type)
  505. }
  506. }
  507. const initCategories_gym = async (type?: "first" | "category" | "level" | "type") => {
  508. if (storeData.value.length > 0 && !["category", "level", "type"].includes(type as any)) {
  509. let result: any = []
  510. if (type === "first" && state.firstTreeId) {
  511. result = storeData.value.find((item: any) => item.id === state.firstTreeId)?.sysMusicScoreCategoriesList || []
  512. } else {
  513. state.firstTreeId = storeData.value[0]?.id
  514. result = storeData.value[0]?.sysMusicScoreCategoriesList || []
  515. }
  516. state.categoryList = result.map((item: any) => {
  517. return {
  518. label: item.name,
  519. value: item.id,
  520. sysMusicScoreCategoriesList: item.sysMusicScoreCategoriesList || []
  521. }
  522. })
  523. state.categoryId = null
  524. state.categoryName = null
  525. state.levelId = null
  526. state.typeId = -1
  527. }
  528. if (state.categoryList.length > 0 && !["level", "type"].includes(type as any)) {
  529. let result: any = []
  530. if (type === "category" && state.categoryId) {
  531. result = state.categoryList.find((item: any) => item.value === state.categoryId)?.sysMusicScoreCategoriesList || []
  532. } else {
  533. state.categoryId = state.categoryList[0]?.value
  534. state.categoryName = state.categoryList[0]?.label
  535. result = state.categoryList[0]?.sysMusicScoreCategoriesList || []
  536. }
  537. state.levelList = result.map((item: any) => {
  538. return {
  539. label: item.name,
  540. value: item.id,
  541. sysMusicScoreCategoriesList: item.sysMusicScoreCategoriesList || []
  542. }
  543. })
  544. await handleGetSubject_gym()
  545. }
  546. if (state.levelList.length > 0) {
  547. let result: any = []
  548. if (type === "level" && state.levelId) {
  549. result = state.levelList.find((item: any) => item.value === state.levelId)?.sysMusicScoreCategoriesList
  550. state.typeId = -1
  551. } else {
  552. state.levelId = state.levelList[0]?.value
  553. result = state.levelList[0]?.sysMusicScoreCategoriesList || []
  554. }
  555. state.typeList = result.map((item: any) => {
  556. return {
  557. label: item.name,
  558. value: item.id
  559. }
  560. })
  561. state.typeList.unshift({
  562. label: "全部",
  563. value: -1
  564. })
  565. }
  566. }
  567. const initCategories_gyt = (type?: "first" | "category" | "level" | "type") => {
  568. if (storeData.value.length > 0 && !["level", "type"].includes(type as any)) {
  569. let result: any = []
  570. if (type === "first" && state.firstTreeId) {
  571. result = storeData.value.find((item: any) => item.id === state.firstTreeId)?.musicSheetCategoriesList || []
  572. } else {
  573. state.firstTreeId = storeData.value[0]?.id
  574. result = storeData.value[0]?.musicSheetCategoriesList || []
  575. }
  576. state.levelList = result.map((item: any) => {
  577. return {
  578. label: item.name,
  579. value: item.id,
  580. musicSheetCategoriesList: item.musicSheetCategoriesList || []
  581. }
  582. })
  583. state.levelId = null
  584. state.typeId = -1
  585. }
  586. if (state.levelList.length > 0) {
  587. let result: any = []
  588. if (type === "level" && state.levelId) {
  589. result = state.levelList.find((item: any) => item.value === state.levelId)?.musicSheetCategoriesList
  590. } else {
  591. state.levelId = state.levelList[0]?.value
  592. result = state.levelList[0]?.musicSheetCategoriesList || []
  593. }
  594. state.typeList = result.map((item: any) => {
  595. return {
  596. label: item.name,
  597. value: item.id
  598. }
  599. })
  600. state.typeList.unshift({
  601. label: "全部",
  602. value: -1
  603. })
  604. state.typeId = -1
  605. }
  606. }
  607. const initCategories_klx = async (type?: "first" | "category" | "level" | "type") => {
  608. if (state.categoryList.length > 0 && !["level", "type", "first"].includes(type as any)) {
  609. let result: any = []
  610. if (type === "category" && state.categoryId) {
  611. result = state.categoryList.find((item: any) => item.value === state.categoryId)?.musicSheetCategoriesList || []
  612. } else {
  613. state.categoryId = state.categoryList[0]?.value
  614. state.categoryName = state.categoryList[0]?.label
  615. result = state.categoryList[0]?.musicSheetCategoriesList || []
  616. }
  617. storeData.value = result.map((item: any) => {
  618. return {
  619. id: item.value,
  620. name: item.label
  621. }
  622. })
  623. }
  624. if (storeData.value.length > 0 && !["level", "type"].includes(type as any)) {
  625. if (type === "first" && state.firstTreeId) {
  626. await handleSelectCondition_klx()
  627. } else {
  628. //
  629. state.firstTreeId = storeData.value[0]?.id
  630. await handleSelectCondition_klx()
  631. }
  632. state.levelId = -1
  633. state.typeId = -1
  634. }
  635. }
  636. const __init = async () => {
  637. await handleAllSearchList()
  638. await handleAllGetSubject()
  639. await handleAllGetList()
  640. await toDetail()
  641. renderStaff()
  642. }
  643. __init()
  644. const handleResh = () => {
  645. if (loading.value || state.finshed) return
  646. state.page = state.page + 1
  647. handleAllGetList()
  648. }
  649. const handleGetList = async () => {
  650. if (loading.value) return
  651. state.listActive = 0
  652. state.showPlayer = false
  653. state.playState = "pause"
  654. state.partNames = []
  655. state.partList = []
  656. state.selectedPartName = ""
  657. state.selectedTrack = ""
  658. state.selectedPartIndex = 0
  659. // state.musicPdfUrl = ""
  660. state.partXmlIndex = 0
  661. document.querySelector(".musicList-container")?.scroll(0, 0)
  662. state.page = 1
  663. state.finshed = false
  664. state.reshing = true
  665. state.list = []
  666. await handleAllGetList()
  667. }
  668. const toDetail = async () => {
  669. const row: any = activeItem.value
  670. if (row.musicSheetType === "SINGLE") {
  671. loading.value = false
  672. state.musicPdfUrl = row.musicPdfUrl
  673. return
  674. }
  675. state.partNames = await getPartNames(row.xmlUrl)
  676. let partList = row.background || []
  677. partList = partList.filter((item: any) => !item.track?.toLocaleUpperCase()?.includes("COMMON"))
  678. partColumns.value = partList.map((item: any, index: number) => {
  679. const instrumentName = getInstrumentName(item.track)
  680. const xmlIndex = state.partNames.findIndex((name: any) => name.trim() === item.track)
  681. let musicPdfUrl = ""
  682. if (userStoreHook.roles === "GYM") {
  683. musicPdfUrl = item.soundMusicPdfUrl
  684. } else if (userStoreHook.roles === "GYT") {
  685. musicPdfUrl = item.musicPdfUrl
  686. } else if (userStoreHook.roles === "KLX") {
  687. musicPdfUrl = item.musicPdfUrl
  688. }
  689. return {
  690. label: item.track + (instrumentName ? `(${instrumentName})` : ""),
  691. instrumentName: instrumentName,
  692. track: item.track,
  693. musicPdfUrl,
  694. xmlIndex,
  695. value: index
  696. }
  697. })
  698. // 初始化数据
  699. // 是否显示总谱
  700. console.log(row, "rows")
  701. if (row.isScoreRender) {
  702. partColumns.value.unshift({
  703. label: "总谱",
  704. instrumentName: null,
  705. track: null,
  706. musicPdfUrl: "",
  707. xmlIndex: 999,
  708. value: 999
  709. })
  710. if (row.defaultScoreRender) {
  711. state.selectedPartIndex = 999
  712. }
  713. }
  714. const defaultShowStaff = partColumns.value.find((item: any) => item.value === state.selectedPartIndex)
  715. state.selectedPartName = defaultShowStaff?.instrumentName
  716. state.selectedTrack = defaultShowStaff?.track
  717. state.partXmlIndex = defaultShowStaff?.xmlIndex
  718. if (row.isScoreRender && row.defaultScoreRender) {
  719. state.musicPdfUrl = row?.musicPdfUrl || ""
  720. } else {
  721. state.musicPdfUrl = defaultShowStaff?.musicPdfUrl || ""
  722. }
  723. }
  724. const getPartNames = async (xmlUrl: string) => {
  725. const partNames: string[] = []
  726. try {
  727. const res: any = await axios.get(xmlUrl)
  728. const xml: any = new DOMParser().parseFromString(res.data, "text/xml")
  729. for (const item of xml.getElementsByTagName("part-name")) {
  730. if (item.textContent) {
  731. partNames.push(item.textContent)
  732. }
  733. }
  734. } catch (error) {
  735. //
  736. }
  737. return partNames.filter((text: string) => text.toLocaleUpperCase() !== "COMMON") || []
  738. }
  739. const musicIframeLoad = async () => {
  740. if (userStoreHook.roles === "GYM") return
  741. const iframeRef: any = document.getElementById("staffIframeRef")
  742. if (iframeRef && iframeRef.contentWindow?.renderXml) {
  743. staffLoading.value = true
  744. const res: any = await axios.get(activeItem.value.xmlUrl)
  745. const parseXmlInfo = getCustomInfo(res.data)
  746. const xml = formatXML(parseXmlInfo.parsedXML)
  747. console.log(activeItem.value, "activeItem.value")
  748. if (activeItem.value.isScoreRender) {
  749. const canSelectTracks: any = []
  750. const background = activeItem.value.background || []
  751. background.forEach((item: any) => {
  752. canSelectTracks.push(item.track)
  753. })
  754. iframeRef.contentWindow.renderXml(xml, activeItem.value.isScoreRender)
  755. } else {
  756. const currentXml = onlyVisible(xml, state.partXmlIndex)
  757. iframeRef.contentWindow.renderXml(currentXml)
  758. }
  759. }
  760. }
  761. const resetRender = async () => {
  762. const iframeRef: any = document.getElementById("staffIframeRef")
  763. if (userStoreHook.roles === "GYM") {
  764. iframeRef.contentWindow.location.replace(getPreViewCloud(activeItem.value.id, state.partXmlIndex))
  765. // state.iframeSrc = getPreViewCloud(activeItem.value.id, state.partXmlIndex)
  766. return
  767. }
  768. if (iframeRef && iframeRef.contentWindow?.renderXml) {
  769. staffLoading.value = true
  770. const res: any = await axios.get(activeItem.value.xmlUrl)
  771. const parseXmlInfo = getCustomInfo(res.data)
  772. const xml = formatXML(parseXmlInfo.parsedXML)
  773. if (activeItem.value.isScoreRender) {
  774. iframeRef.contentWindow.renderXml(xml, activeItem.value.isScoreRender)
  775. } else {
  776. const currentXml = onlyVisible(xml, state.partXmlIndex)
  777. iframeRef.contentWindow.renderXml(currentXml)
  778. }
  779. }
  780. }
  781. const renderStaff = async () => {
  782. try {
  783. if (state.musicPdfUrl) {
  784. state.iframeSrc = "/pdf/web/viewer.html?file=" + encodeURIComponent(state.musicPdfUrl) + "&t=" + Date.now()
  785. // https://cdn.oss.dayaedu.com/daya202409/UOFW4q5.pdf
  786. // https://cdn.oss.dayaedu.com/daya202409/UOFVK2A.pdf
  787. // https://cdn.oss.dayaedu.com/daya202409/UODQffO.pdf
  788. } else {
  789. if (userStoreHook.roles === "GYM") {
  790. state.iframeSrc = getPreViewCloud(activeItem.value.id, state.partXmlIndex)
  791. } else {
  792. state.iframeSrc = `/osmd/index.html`
  793. }
  794. }
  795. } catch (error) {
  796. //
  797. }
  798. }
  799. /** 音频控制 */
  800. const handleChangeAudio = (type: "play" | "pause" | "pre" | "next") => {
  801. if (type === "play") {
  802. state.playState = "play"
  803. } else if (type === "pause") {
  804. state.playState = "pause"
  805. } else if (type === "pre") {
  806. if (state.list[state.listActive - 1]) {
  807. handlePlay(state.list[state.listActive - 1])
  808. }
  809. } else if (type === "next") {
  810. if (state.list[state.listActive + 1]) {
  811. handlePlay(state.list[state.listActive + 1])
  812. }
  813. }
  814. }
  815. /** 播放曲目 */
  816. const handlePlay = (item: any) => {
  817. const index = state.list.findIndex((_item: any) => _item.id === item.id)
  818. if (index > -1) {
  819. if (state.listActive === index) {
  820. state.playState = state.playState === "play" ? "pause" : "play"
  821. } else {
  822. state.playState = "play"
  823. }
  824. state.showPlayer = true
  825. state.listActive = index
  826. }
  827. }
  828. // // 多个文件下载
  829. const downLoadMultiFile = (files: any, filesName: string) => {
  830. const zip = new JSZip()
  831. // const result = []
  832. // console.log(files)
  833. for (const i in files) {
  834. zip.file(files[i].name, files[i].url, { binary: true })
  835. }
  836. zip.generateAsync({ type: "blob" }).then(res => {
  837. saveAs(res, filesName ? filesName + ".zip" : `文件夹${Date.now()}.zip`)
  838. })
  839. downloadStatus.value = false
  840. }
  841. const showLoading = async (e: any) => {
  842. if (e.data?.api === "musicStaffRender") {
  843. const musicName =
  844. activeItem.value.name +
  845. ((activeItem.value.musicSheetType === "CONCERT" && state.selectedPartName) || state.selectedTrack
  846. ? `(${state.selectedPartName || state.selectedTrack})`
  847. : "")
  848. try {
  849. const osmdImg = e.data.osmdImg
  850. const imgs = []
  851. for (let i = 0; i < osmdImg.length; i++) {
  852. const img = await svgtoblob(osmdImg[i].img, osmdImg[i].width, osmdImg[i].height, musicName)
  853. imgs.push({
  854. url: img,
  855. name: i + 1 + ".png"
  856. })
  857. }
  858. state.imgs = imgs
  859. } catch (e) {
  860. // console.log(e);
  861. }
  862. staffLoading.value = e.data.loading
  863. loading.value = e.data.loading
  864. }
  865. }
  866. const searchContent = async () => {
  867. await toDetail()
  868. if (activeItem.value?.id) {
  869. if (state.musicPdfUrl) {
  870. staffLoading.value = true
  871. renderStaff()
  872. } else {
  873. // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
  874. if (state.iframeSrc.indexOf("pdf/web") !== -1) {
  875. renderStaff()
  876. } else {
  877. resetRender()
  878. }
  879. }
  880. }
  881. }
  882. /** 下载图片 */
  883. const onDownload = () => {
  884. if (downloadStatus.value || staffLoading.value) return
  885. const musicName =
  886. activeItem.value.name +
  887. ((activeItem.value.musicSheetType === "CONCERT" && state.selectedPartName) || state.selectedTrack
  888. ? `(${state.selectedPartName || state.selectedTrack})`
  889. : "")
  890. downloadStatus.value = true
  891. if (state.musicPdfUrl) {
  892. // 发起Fetch请求
  893. fetch(state.musicPdfUrl)
  894. .then(response => response.blob())
  895. .then(blob => {
  896. saveAs(blob, musicName)
  897. downloadStatus.value = false
  898. })
  899. .catch(() => {
  900. ElMessage.error("下载失败")
  901. downloadStatus.value = false
  902. })
  903. } else {
  904. downLoadMultiFile(state.imgs, musicName)
  905. }
  906. }
  907. // 白板的批注打开时暂停播放
  908. watch(
  909. () => [whitePenShow.value, penShow.value],
  910. () => {
  911. if (whitePenShow.value || penShow.value) {
  912. handleChangeAudio("pause")
  913. }
  914. }
  915. )
  916. onMounted(() => {
  917. const obv = new IntersectionObserver(entries => {
  918. if (entries[0].intersectionRatio > 0) {
  919. handleResh()
  920. }
  921. })
  922. obv.observe(spinRef.value)
  923. window.addEventListener("message", showLoading)
  924. })
  925. return () => (
  926. <NavContainer navs={navs}>
  927. {/* <ElScrollbar class="elScrollbar"> */}
  928. <div class={styles.cloudPractice}>
  929. <div class={styles.leftContainer}>
  930. <div class={styles.details}>
  931. {storeData.value.length > 0 && (
  932. <ElScrollbar class={styles.leftSection}>
  933. {/* 基 础 云 练 */}
  934. {storeData.value.map((item: any) => (
  935. <div
  936. class={[styles.leftSection_item, item.id === state.firstTreeId && styles.leftSection_item__active]}
  937. onClick={async () => {
  938. if (loading.value) return
  939. state.firstTreeId = item.id
  940. await setDefaultData("first")
  941. await handleGetList()
  942. searchContent()
  943. }}
  944. >
  945. {item.name}
  946. </div>
  947. ))}
  948. </ElScrollbar>
  949. )}
  950. <div class={[styles.musicList, "musicList-container"]}>
  951. <div class={styles.searchHeader}>
  952. {state.categoryList.length > 1 && (
  953. <div class={[styles.categorySection]}>
  954. <NPopselect
  955. placement="bottom-start"
  956. disabled={loading.value}
  957. options={state.categoryList}
  958. v-model:value={state.categoryId}
  959. onUpdate:value={async (val: any) => {
  960. const item = state.categoryList.find((item: any) => item.value === val)
  961. if (item) {
  962. state.categoryName = item.label
  963. state.categoryId = item.value
  964. await setDefaultData("category")
  965. await handleGetList()
  966. searchContent()
  967. }
  968. }}
  969. onUpdate:show={(value: any) => {
  970. state.categoryShow = value
  971. }}
  972. trigger="click"
  973. class={"PopSelect"}
  974. >
  975. <span class={[styles.iconTagName, state.categoryShow && styles.show]}>
  976. <span>{state.categoryName}</span>
  977. </span>
  978. </NPopselect>
  979. </div>
  980. )}
  981. <div class={styles.searchMore}>
  982. <div class={styles.searchSection}>
  983. <Dictionary
  984. clearable={false}
  985. popperClass="classTypePopper"
  986. v-model={state.subjectId}
  987. height={42}
  988. // disabled={loading.value}
  989. options={state.subjectList}
  990. placeholder="全部声部"
  991. onChange={async () => {
  992. await handleGetList()
  993. searchContent()
  994. }}
  995. />
  996. {state.levelList.length ? (
  997. <Dictionary
  998. clearable={false}
  999. popperClass="classTypePopper"
  1000. v-model={state.levelId}
  1001. height={42}
  1002. // disabled={loading.value}
  1003. options={state.levelList}
  1004. placeholder="级别"
  1005. onChange={async () => {
  1006. setDefaultData("level")
  1007. await handleGetList()
  1008. searchContent()
  1009. }}
  1010. />
  1011. ) : null}
  1012. {state.typeList.length > 0 ? (
  1013. <Dictionary
  1014. clearable={false}
  1015. popperClass="classTypePopper"
  1016. v-model={state.typeId}
  1017. height={42}
  1018. // disabled={loading.value}
  1019. options={state.typeList}
  1020. propsOpt={{
  1021. labelField: "name",
  1022. valueField: "id"
  1023. }}
  1024. placeholder="分类"
  1025. onChange={async () => {
  1026. await handleGetList()
  1027. searchContent()
  1028. }}
  1029. />
  1030. ) : null}
  1031. </div>
  1032. <div
  1033. class={[styles.btnSearch, state.searchStatus && styles.btnSearchActive]}
  1034. onClick={() => (state.searchStatus = !state.searchStatus)}
  1035. ></div>
  1036. </div>
  1037. {state.searchStatus && (
  1038. <MyInput
  1039. class="queryCp"
  1040. v-model={state.queryStr}
  1041. height={42}
  1042. placeholder="请输入曲目关键词"
  1043. onKeyup={async (e: any) => {
  1044. if (e.code === "Enter" || e.key === "Enter") {
  1045. await handleGetList()
  1046. searchContent()
  1047. }
  1048. }}
  1049. onHandleQuery={async () => {
  1050. await handleGetList()
  1051. searchContent()
  1052. }}
  1053. clearable
  1054. />
  1055. )}
  1056. </div>
  1057. <div class={[styles.wrapList, !state.list.length && !loading.value && styles.wrapListEmpty]}>
  1058. {state.list.map((item: any, index: number) => (
  1059. <div
  1060. class={[styles.item, index === state.listActive && styles.active]}
  1061. onClick={async () => {
  1062. if (index === state.listActive) {
  1063. return
  1064. }
  1065. state.listActive = index
  1066. state.selectedPartIndex = 0
  1067. state.partXmlIndex = 0
  1068. searchContent()
  1069. }}
  1070. >
  1071. <div class={styles.itemInfo}>
  1072. <div class={styles.img}>
  1073. <NImage
  1074. lazy
  1075. objectFit="cover"
  1076. previewDisabled={true}
  1077. src={item.titleImg || icon_default}
  1078. onLoad={(e: any) => {
  1079. ;(e.target as any).dataset.loaded = "true"
  1080. }}
  1081. />
  1082. <PlayLoading
  1083. class={[state.listActive === index && state.playState === "play" ? "" : styles.showPlayLoading]}
  1084. />
  1085. </div>
  1086. <div class={styles.title}>
  1087. <div class={styles.titleName}>
  1088. <ellipsisScroll title={item.name} />
  1089. </div>
  1090. </div>
  1091. </div>
  1092. <div class={styles.btnSection}>
  1093. <div
  1094. class={styles.btn}
  1095. onClick={(e: any) => {
  1096. e.stopPropagation()
  1097. handlePlay(item)
  1098. if (state.listActive !== index) {
  1099. resetRender()
  1100. }
  1101. }}
  1102. >
  1103. {state.listActive === index && (
  1104. <>
  1105. {state.playState === "pause" ? "播放" : "暂停"}
  1106. <img src={state.playState === "pause" ? iconBtnPlay : (iconBtnPause as any)} />
  1107. </>
  1108. )}
  1109. {state.listActive !== index && (
  1110. <>
  1111. 播放
  1112. <img src={iconBtnPlay as any} />
  1113. </>
  1114. )}
  1115. </div>
  1116. </div>
  1117. </div>
  1118. ))}
  1119. {!state.list.length && !loading.value && (
  1120. <ElEmpty class={styles.empty} image={require("@/img/layout/empty.png")} description="暂无内容" />
  1121. )}
  1122. <div ref={spinRef} class={[styles.loadingWrap, state.finshed && styles.showLoading]}>
  1123. <NSpin show={true} stroke="#FF531C"></NSpin>
  1124. </div>
  1125. </div>
  1126. </div>
  1127. </div>
  1128. </div>
  1129. <div class={styles.rightContainer}>
  1130. {/* <i class={styles.leftArrow}></i> */}
  1131. <NSpin show={staffLoading.value} stroke="#FF531C">
  1132. {activeItem.value?.id ? (
  1133. state.musicPdfUrl ? (
  1134. <div class={[styles.staffImgs]}>
  1135. <iframe
  1136. style={{
  1137. // opacity: loading.value ? 0 : 1,
  1138. width: "100%",
  1139. height: "100%"
  1140. }}
  1141. src={state.iframeSrc}
  1142. onLoad={() => {
  1143. // 判断是用哪个渲染的
  1144. loading.value = false
  1145. staffLoading.value = false
  1146. }}
  1147. ></iframe>
  1148. </div>
  1149. ) : (
  1150. <>
  1151. <div class={styles.musicName}>
  1152. {activeItem.value.name}
  1153. {activeItem.value.musicSheetType === "CONCERT" &&
  1154. (state.selectedPartName || state.selectedTrack ? `(${state.selectedPartName || state.selectedTrack})` : "")}
  1155. </div>
  1156. <div class={[styles.staffImgs]}>
  1157. <iframe
  1158. id="staffIframeRef"
  1159. style={{
  1160. // opacity: loading.value ? 0 : 1,
  1161. width: "100%",
  1162. height: "100%"
  1163. }}
  1164. src={state.iframeSrc}
  1165. onLoad={musicIframeLoad}
  1166. ></iframe>
  1167. </div>
  1168. </>
  1169. )
  1170. ) : (
  1171. <div class={[styles.staffImgs, !loading.value && !activeItem.value?.id && styles.staffImgsEmpty]}>
  1172. {!loading.value && !activeItem.value?.id && (
  1173. <ElEmpty class={styles.empty} image={require("@/img/layout/empty.png")} description="暂无内容" />
  1174. )}
  1175. </div>
  1176. )}
  1177. </NSpin>
  1178. <img
  1179. style={{
  1180. display: activeItem.value?.id ? "" : "none"
  1181. }}
  1182. class={[styles.goBtn]}
  1183. src={btnSubmit as any}
  1184. onClick={() => {
  1185. handleChangeAudio("pause")
  1186. goToCloud(activeItem.value.id, state.partXmlIndex)
  1187. }}
  1188. />
  1189. <div class={styles.rightBtns} style={{ display: activeItem.value.id ? "" : "none" }}>
  1190. <div style={{ display: state.musicPdfUrl || state.imgs.length > 0 ? "" : "none" }}>
  1191. <NTooltip showArrow={false}>
  1192. {{
  1193. trigger: () => (
  1194. <img
  1195. onClick={onDownload}
  1196. class={[styles.transBtn, (downloadStatus.value || staffLoading.value) && styles.disableBtn]}
  1197. src={iconDownload as any}
  1198. />
  1199. ),
  1200. default: "下载曲谱"
  1201. }}
  1202. </NTooltip>
  1203. </div>
  1204. <div style={{ display: activeItem.value.musicSheetType === "CONCERT" ? "" : "none" }}>
  1205. <NPopselect
  1206. options={partColumns.value}
  1207. placement="bottom-end"
  1208. trigger="click"
  1209. v-model:value={state.selectedPartIndex}
  1210. scrollable
  1211. onUpdate:value={async (value: any) => {
  1212. const item = partColumns.value.find((item: any) => item.value === value)
  1213. state.selectedPartIndex = value
  1214. state.selectedPartName = item.instrumentName
  1215. state.selectedTrack = item.track
  1216. state.partXmlIndex = item.xmlIndex
  1217. nextTick(() => {
  1218. let tempPdf = ""
  1219. if (activeItem.value?.isScoreRender && value === 999) {
  1220. if (activeItem.value?.musicPdfUrl) {
  1221. tempPdf = activeItem.value?.musicPdfUrl
  1222. }
  1223. } else {
  1224. tempPdf = item.musicPdfUrl
  1225. }
  1226. if (tempPdf) {
  1227. state.musicPdfUrl = tempPdf
  1228. staffLoading.value = true
  1229. renderStaff()
  1230. } else {
  1231. state.musicPdfUrl = ""
  1232. loading.value = true
  1233. // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
  1234. if (state.iframeSrc.indexOf("pdf/web") !== -1) {
  1235. renderStaff()
  1236. } else {
  1237. resetRender()
  1238. }
  1239. }
  1240. })
  1241. }}
  1242. class={["PopSelect", "PopSelectPart"]}
  1243. >
  1244. {{
  1245. empty: () => "暂无数据",
  1246. default: () => (
  1247. <NTooltip showArrow={false}>
  1248. {{
  1249. trigger: () => <img class={styles.transBtn} src={iconTransfer as any} />,
  1250. default: "切换声轨"
  1251. }}
  1252. </NTooltip>
  1253. )
  1254. }}
  1255. </NPopselect>
  1256. </div>
  1257. </div>
  1258. </div>
  1259. </div>
  1260. {/* </ElScrollbar> */}
  1261. {state.list.length !== 0 && activeItem.value.audioFileUrl && (
  1262. <PlayItem
  1263. show={state.showPlayer}
  1264. playState={state.playState}
  1265. songPrevNextStatus={songPrevNextStatus.value}
  1266. item={activeItem.value}
  1267. onChange={value => handleChangeAudio(value)}
  1268. onShow={(status: boolean) => {
  1269. state.showPlayer = status
  1270. }}
  1271. />
  1272. )}
  1273. <PracticeForm v-model={isPracticeShow.value} practiceUrl={practiceUrl.value} onClose={handlePracticeClose} />
  1274. </NavContainer>
  1275. )
  1276. }
  1277. })