cloudPractice.tsx 54 KB

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