index.tsx 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  1. import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef, ComputedRef, nextTick, defineAsyncComponent } from "vue";
  2. import styles from "./index.module.less";
  3. import iconBack from "./image/icon-back.png";
  4. import listImg from "./image/list.png";
  5. import iconMode from "./image/mode.png";
  6. import { headImg } from "./image";
  7. import { Badge, Circle, Popover, Popup, showConfirmDialog, showToast, NoticeBar } from "vant";
  8. import Speed from "./speed";
  9. import { evaluatingData, handleStartEvaluat } from "/src/view/evaluating";
  10. import Settting from "./settting";
  11. import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay, IPlayState, refreshMusicSvg, EnumMusicRenderType } from "/src/state";
  12. import { getAudioCurrentTime } from "/src/view/audio-list";
  13. import { followData, toggleFollow } from "/src/view/follow-practice";
  14. import { api_back } from "/src/helpers/communication";
  15. import MusicType from "./music-type";
  16. import ModeTypeMode from "../component/mode-type-mode";
  17. import { getQuery } from "/src/utils/queryString";
  18. import { storeData } from "/src/store";
  19. import TeacherTop from "../custom-plugins/guide-page/teacher-top";
  20. import StudentTop from "../custom-plugins/guide-page/student-top";
  21. import { HANDLE_WORK_ADD } from "../custom-plugins/work-index";
  22. import { browser } from "/src/utils";
  23. import store from "store";
  24. import "../component/the-modal-tip/index.module.less";
  25. import { metronomeData } from "../../helpers/metronome";
  26. import { toggleMusicSheet } from "/src/view/plugins/toggleMusicSheet";
  27. import useDrag from "/src/view/plugins/useDrag/index";
  28. import Dragbom from "/src/view/plugins/useDrag/dragbom";
  29. import { getGuidance, setGuidance } from "../custom-plugins/guide-page/api";
  30. // import ModeView from "./modeView";
  31. import { smoothAnimationState } from "../view-detail/smoothAnimation";
  32. import { isMusicList, musicListShow } from "../component/the-music-list";
  33. import { EvaluatingDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
  34. import { fingerRef } from "/src/page-instrument/view-detail/index"
  35. const ModeView = defineAsyncComponent(() =>
  36. import('./modeView')
  37. )
  38. /** 头部数据和方法 */
  39. export const headTopData = reactive({
  40. /** 模式 */
  41. modeType: "" as "init" | "show",
  42. /** 显示返回按钮 */
  43. showBack: true,
  44. /** 设置弹窗 */
  45. settingMode: false,
  46. /** 切换模式 */
  47. handleChangeModeType(value: "practise" | "follow" | "evaluating") {
  48. // 后台设置为不能评测
  49. if (value === "evaluating" && !state.enableEvaluation) return;
  50. // 打击乐&节奏练习不支持跟练模式
  51. if (value === "follow" && state.isPercussion) return;
  52. // 跟练模式,光标只有音符模式,无节拍模式
  53. if (value === "follow" && metronomeData.cursorMode === 2) {
  54. metronomeData.cursorMode = 1;
  55. }
  56. if (value === "practise") {
  57. // state.playIngSpeed = state.speed
  58. }
  59. if (value === "evaluating") {
  60. // 如果延迟检测资源还在加载中,给出提示
  61. if (!evaluatingData.jsonLoadDone) {
  62. evaluatingData.jsonLoading = true;
  63. state.audioDone && showToast("资源加载中,请稍后"); //音频资源加载完之后才提示
  64. return;
  65. }
  66. // 如果是pc端, 评测模式暂不可用
  67. if (state.platform === IPlatform.PC) {
  68. showConfirmDialog({
  69. className: "modalTip",
  70. title: "温馨提示",
  71. message: "该功能暂未开放,敬请期待!",
  72. showCancelButton: false,
  73. });
  74. return;
  75. }
  76. // 评测模式,只有一行谱模式
  77. // if (!state.isSingleLine) {
  78. // state.isSingleLine = true;
  79. // refreshMusicSvg();
  80. // }
  81. smoothAnimationState.isShow.value = false; // 隐藏旋律线
  82. state.playIngSpeed = state.originSpeed;
  83. handleStartEvaluat();
  84. // 开发模式,把此处打开
  85. // state.modeType = "evaluating";
  86. // evaluatingData.rendered = true;
  87. // evaluatingData.soundEffectMode = true;
  88. } else if (value === "follow") {
  89. // 跟练模式,只有一行谱模式
  90. if (!state.isSingleLine) {
  91. state.isSingleLine = true;
  92. refreshMusicSvg();
  93. }
  94. smoothAnimationState.isShow.value = false;
  95. toggleFollow();
  96. }
  97. headTopData.modeType = "show";
  98. },
  99. // 改变模式之前的状态
  100. oldPlayType: "play",
  101. // 记录切换模式前的状态
  102. oldModeType: "practise" as "practise" | "follow" | "evaluating",
  103. });
  104. export const headData = reactive({
  105. speedShow: false,
  106. musicTypeShow: false,
  107. });
  108. let resetBtn: ComputedRef<{
  109. display: boolean;
  110. disabled: boolean;
  111. }>;
  112. // 点击切换的时候才触发提醒
  113. let isClickMode = false;
  114. /**
  115. * 处理模式切换
  116. * @param oldPlayType 没改变之前的播放模式
  117. * @param oldPlaySource 没改变之前的播放类型
  118. * @param isforceReset 是否强制刷新播放状态 模式times时值改变时候也刷新
  119. */
  120. export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: IPlayState, isforceReset?: boolean) {
  121. const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource);
  122. // 没有切换的时候 不处理下面的
  123. if (isModeChange) {
  124. try {
  125. metronomeData.metro.calculation(state.times);
  126. } catch (error) {}
  127. console.log("重新之后的times", state.times, state.fixtime);
  128. }
  129. if (isModeChange || isforceReset) {
  130. // 重置播放状态
  131. handleRessetState();
  132. // 隐藏重播按钮
  133. resetBtn && (resetBtn.value.display = false);
  134. }
  135. // 当模式改变的时候 放在这里是因为需要等谱面加载完成之后再提示(点击按钮模式切换才提示)
  136. if (isClickMode) {
  137. showToast({
  138. message: state.playType === "play" ? "已切换为演奏场景" : "已切换为演唱场景",
  139. position: "top",
  140. className: "selectionToast",
  141. });
  142. isClickMode = false;
  143. }
  144. }
  145. // 模式切换之后重新给times赋值
  146. function modeChangeHandleTimes(oldPlayType: "play" | "sing", oldPlaySource: IPlayState) {
  147. const playType = state.playType;
  148. const playSource = state.playSource;
  149. const { notBeatFixtime, xmlMp3BeatFixTime, difftime } = state.times[0];
  150. const { isOpenMetronome, isSingOpenMetronome } = state;
  151. // 演奏向演唱切
  152. if (oldPlayType === "play" && playType === "sing") {
  153. if (playSource === "mingSong") {
  154. // 唱名文件也要加上弱起时间 他们制作曲子加了弱起时间 注意这修改了之后给总控平台的时值也需要改
  155. state.fixtime = difftime;
  156. state.times.map((item) => {
  157. item.time = item.xmlNoteTime + difftime;
  158. item.endtime = item.xmlNoteEndTime + difftime;
  159. item.fixtime = difftime;
  160. });
  161. return true;
  162. } else {
  163. //演奏开了节拍器,演唱没开节拍器
  164. if (isOpenMetronome && !isSingOpenMetronome) {
  165. state.fixtime = notBeatFixtime;
  166. state.times.map((item) => {
  167. item.time = item.notBeatTime;
  168. item.endtime = item.notBeatEndTime;
  169. item.fixtime = notBeatFixtime;
  170. });
  171. return true;
  172. } else if (!isOpenMetronome && isSingOpenMetronome) {
  173. state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  174. state.times.map((item) => {
  175. item.time = item.notBeatTime + xmlMp3BeatFixTime;
  176. item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
  177. item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  178. });
  179. return true;
  180. }
  181. }
  182. } else if (oldPlayType === "sing" && playType === "play") {
  183. // 演唱向演奏切
  184. if (oldPlaySource === "mingSong") {
  185. // 有节拍器
  186. if (isOpenMetronome) {
  187. state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  188. state.times.map((item) => {
  189. item.time = item.notBeatTime + xmlMp3BeatFixTime;
  190. item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
  191. item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  192. });
  193. return true;
  194. } else {
  195. state.fixtime = notBeatFixtime;
  196. state.times.map((item) => {
  197. item.time = item.notBeatTime;
  198. item.endtime = item.notBeatEndTime;
  199. item.fixtime = notBeatFixtime;
  200. });
  201. return true;
  202. }
  203. }
  204. // 演奏开了节拍器,演唱没开节拍器
  205. if (isOpenMetronome && !isSingOpenMetronome) {
  206. state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  207. state.times.map((item) => {
  208. item.time = item.notBeatTime + xmlMp3BeatFixTime;
  209. item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
  210. item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  211. });
  212. return true;
  213. } else if (!isOpenMetronome && isSingOpenMetronome) {
  214. state.fixtime = notBeatFixtime;
  215. state.times.map((item) => {
  216. item.time = item.notBeatTime;
  217. item.endtime = item.notBeatEndTime;
  218. item.fixtime = notBeatFixtime;
  219. });
  220. return true;
  221. }
  222. } else if (oldPlayType === "sing" && playType === "sing") {
  223. // 演唱之间切换
  224. // 切到唱名时候
  225. if (playSource === "mingSong") {
  226. // 唱名文件也要加上弱起时间 他们制作曲子加了弱起时间 注意这修改了之后给总控平台的时值也需要改
  227. state.fixtime = difftime;
  228. state.times.map((item) => {
  229. item.time = item.xmlNoteTime + difftime;
  230. item.endtime = item.xmlNoteEndTime + difftime;
  231. item.fixtime = difftime;
  232. });
  233. return true;
  234. } else if (oldPlaySource === "mingSong") {
  235. // 有节拍器
  236. if (isSingOpenMetronome) {
  237. state.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  238. state.times.map((item) => {
  239. item.time = item.notBeatTime + xmlMp3BeatFixTime;
  240. item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime;
  241. item.fixtime = notBeatFixtime + xmlMp3BeatFixTime;
  242. });
  243. return true;
  244. } else {
  245. state.fixtime = notBeatFixtime;
  246. state.times.map((item) => {
  247. item.time = item.notBeatTime;
  248. item.endtime = item.notBeatEndTime;
  249. item.fixtime = notBeatFixtime;
  250. });
  251. return true;
  252. }
  253. }
  254. }
  255. return false;
  256. }
  257. export default defineComponent({
  258. name: "header-top",
  259. emits: ["close"],
  260. setup(props, { emit }) {
  261. const query = getQuery();
  262. // 是否显示引导
  263. const showGuide = ref(false);
  264. const showStudentGuide = ref(false);
  265. const showWebGuide = ref(true);
  266. let displayFingeringCache = false; // 指法缓存
  267. /** 设置按钮 */
  268. const settingBtn = computed(() => {
  269. // 音频播放中 禁用
  270. if (state.playState === "play") return { display: true, disabled: true };
  271. // 评测开始 禁用, 跟练开始 禁用
  272. if (evaluatingData.startBegin || followData.start) return { display: true, disabled: true };
  273. return {
  274. display: true,
  275. disabled: false,
  276. };
  277. });
  278. /** 转谱按钮 */
  279. const converBtn = computed(() => {
  280. // 音频播放中 禁用
  281. if (state.playState === "play") return { display: true, disabled: true };
  282. // 评测开始 禁用
  283. if (evaluatingData.startBegin || followData.start) return { display: true, disabled: true };
  284. return {
  285. disabled: false,
  286. display: true,
  287. };
  288. });
  289. /** 速度按钮 */
  290. const speedBtn = computed(() => {
  291. // 选择模式, 跟练模式 不显示
  292. //if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: true };
  293. if (state.modeType === "follow") return { display: false, disabled: true };
  294. // 评测模式, 音频播放中 禁用
  295. if (state.modeType === "evaluating" || state.playState === "play") return { display: true, disabled: true };
  296. return {
  297. disabled: false,
  298. display: true,
  299. };
  300. });
  301. /** 节拍器按钮 */
  302. const metronomeBtn = computed(() => {
  303. // 选择模式 不显示
  304. //if (headTopData.modeType !== "show") return { display: false, disabled: true };
  305. // 音频播放中 禁用
  306. if (state.playState === "play") return { display: true, disabled: true };
  307. return {
  308. disabled: false,
  309. display: true,
  310. };
  311. });
  312. /** 指法按钮 */
  313. const fingeringBtn = computed(() => {
  314. // 后台设置不显示指法
  315. if (!state.isShowFingering) return { display: true, disabled: true };
  316. // 没有指法 选择模式 评测模式 跟练模式 不显示
  317. //if (headTopData.modeType !== "show" || !state.fingeringInfo.name || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  318. if (!state.fingeringInfo.name || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  319. // 音频播放中 禁用
  320. if (state.playState === "play") return { display: true, disabled: true };
  321. return {
  322. disabled: false,
  323. display: true,
  324. };
  325. });
  326. /** 摄像头按钮 */
  327. const cameraBtn = computed(() => {
  328. // 选择模式 不显示
  329. if (headTopData.modeType !== "show" || state.modeType !== "evaluating") return { display: false, disabled: true };
  330. // 音频播放中 禁用
  331. if (state.playState === "play") return { display: true, disabled: true };
  332. return {
  333. disabled: false,
  334. display: true,
  335. };
  336. });
  337. /** 选段按钮 */
  338. const selectBtn = computed(() => {
  339. // 选择模式 不显示
  340. //if (headTopData.modeType !== "show" || ["follow"].includes(state.modeType)) return { display: false, disabled: true };
  341. if (["follow"].includes(state.modeType)) return { display: false, disabled: true };
  342. // 音频播放中 禁用
  343. if (state.playState === "play" || query.workRecord) return { display: true, disabled: true };
  344. return {
  345. disabled: false,
  346. display: true,
  347. };
  348. });
  349. /** 原声按钮 */
  350. const originBtn = computed(() => {
  351. // 没有音源不显示
  352. if (state.noMusicSource) return { display: false, disabled: false };
  353. // 选择模式,跟练模式 不显示
  354. //if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: false };
  355. if (state.modeType === "follow") return { display: false, disabled: false };
  356. // 评测开始 禁用
  357. if (state.modeType === "evaluating") return { display: false, disabled: true };
  358. if (!state.isAppPlay) {
  359. if (state.playType === "play") {
  360. // 原声, 伴奏 少一个,就不能切换
  361. if (state.music && state.accompany) return { display: true, disabled: false };
  362. } else {
  363. // 播放过程中不能切换
  364. if (state.playState === "play") {
  365. return { display: true, disabled: true };
  366. }
  367. // 范唱
  368. let index = 0;
  369. state.fanSong && index++;
  370. state.banSong && index++;
  371. state.mingSong && index++;
  372. if (index > 1) {
  373. return { display: true, disabled: false };
  374. }
  375. }
  376. }
  377. return {
  378. disabled: true,
  379. display: true,
  380. };
  381. });
  382. /** 播放类型按钮 */
  383. const playTypeBtn = computed(() => {
  384. // 选择模式,跟练模式,评测模式 不显示
  385. //if (headTopData.modeType !== "show" || state.modeType === "follow" || state.modeType === "evaluating" || query.workRecord) return { display: false, disabled: false };
  386. if (state.modeType === "follow" || state.modeType === "evaluating" || query.workRecord) return { display: false, disabled: false };
  387. if (!state.isAppPlay) {
  388. let index = 0;
  389. state.music && index++;
  390. state.accompany && index++;
  391. let songIndex = 0;
  392. state.fanSong && songIndex++;
  393. state.banSong && songIndex++;
  394. state.mingSong && songIndex++;
  395. // 演唱和演奏 都有数据的时间不禁用
  396. if (songIndex > 0 && index > 0) {
  397. // 音频播放中 禁用
  398. if (state.playState === "play") {
  399. return { display: true, disabled: true };
  400. }
  401. return { display: true, disabled: false };
  402. }
  403. }
  404. return {
  405. disabled: false,
  406. display: false,
  407. };
  408. });
  409. /** 模式切换按钮 */
  410. const toggleBtn = computed(() => {
  411. // 老师端,打击乐&节奏练习不显示
  412. if (state.isPercussion && state.platform === IPlatform.PC) return { display: false, disabled: false };
  413. if(state.isCombineRender) return { display: false, disabled: false };
  414. // 没有音源不显示
  415. if (state.noMusicSource) return { display: false, disabled: false };
  416. // 不是演奏模式 影藏
  417. if (state.playType !== "play") return { display: false, disabled: false };
  418. // 选择模式, url设置模式 不显示
  419. if (headTopData.modeType !== "show" || !headTopData.showBack) return { display: false, disabled: false };
  420. // 跟练开始, 评测开始 播放开始 隐藏
  421. if (state.playState == "play" || followData.start || evaluatingData.startBegin) return { display: false, disabled: false };
  422. return {
  423. display: true,
  424. disabled: false,
  425. };
  426. });
  427. /** 播放按钮 */
  428. const playBtn = computed(() => {
  429. // 没有音源不显示
  430. if (state.noMusicSource) return { display: false, disabled: false };
  431. // 选择模式 不显示
  432. if (headTopData.modeType !== "show") return { display: false, disabled: false };
  433. // 评测模式 不显示,跟练模式 不显示
  434. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  435. // midi音频未初始化完成不可点击
  436. if (state.isAppPlay && state.midiPlayIniting) return { display: true, disabled: true };
  437. return {
  438. display: true,
  439. disabled: false,
  440. };
  441. });
  442. /** 重播按钮 */
  443. resetBtn = computed(() => {
  444. // 没有音源不显示
  445. if (state.noMusicSource) return { display: false, disabled: false };
  446. // 选择模式 不显示
  447. if (headTopData.modeType !== "show") return { display: false, disabled: false };
  448. // 评测模式 不显示,跟练模式 不显示
  449. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  450. // 播放状态 不显示
  451. if (state.playState === "play") return { display: false, disabled: true };
  452. // 播放进度为0 不显示
  453. const currentTime = getAudioCurrentTime();
  454. // midi音频未初始化完成不可点击
  455. if (state.isAppPlay && state.midiPlayIniting) return { display: false, disabled: true };
  456. if (!currentTime) return { display: false, disabled: true };
  457. return {
  458. display: true,
  459. disabled: false,
  460. };
  461. });
  462. const isAllBtns = computed(() => {
  463. const flag = converBtn.value.display && speedBtn.value.display && selectBtn.value.display && originBtn.value.display && toggleBtn.value.display && showGuide.value;
  464. return flag;
  465. });
  466. const isAllBtnsStudent = computed(() => {
  467. const flag = converBtn.value.display && speedBtn.value.display && selectBtn.value.display && originBtn.value.display && toggleBtn.value.display && showStudentGuide.value;
  468. return flag;
  469. });
  470. const showGuideIndex = computed(() => {
  471. // 从课堂乐器学生端课件预览默认不显示会员
  472. if (storeData.user.vipMember || state.paymentType === "FREE" || query.showCourseMember === "true") {
  473. // 学生端
  474. return true;
  475. } else {
  476. // vip
  477. return false;
  478. }
  479. });
  480. const browInfo = browser();
  481. /** 返回 */
  482. const handleBack = () => {
  483. HANDLE_WORK_ADD();
  484. // 不在APP中,
  485. if (!storeData.isApp) {
  486. window.parent.postMessage(
  487. {
  488. api: "back",
  489. },
  490. "*"
  491. );
  492. window.close();
  493. return;
  494. }
  495. if ((browInfo.iPhone || browInfo.ios) && query.workRecord) {
  496. setTimeout(() => {
  497. api_back();
  498. }, 550);
  499. return;
  500. }
  501. api_back();
  502. };
  503. /** 根据参数设置模式 */
  504. const getQueryModelSetModelType = () => {
  505. /** 作业模式 start, 如果为作业模式不处理,让作业模块处理 */
  506. if (query.workRecord) {
  507. return;
  508. }
  509. /** 作业模式 end */
  510. if (state.defaultModeType == 1) {
  511. headTopData.handleChangeModeType("practise");
  512. // if (state.platform === IPlatform.PC || state.isPreView) {
  513. // headTopData.showBack = false;
  514. // }
  515. if (state.isPreView) {
  516. headTopData.showBack = false;
  517. }
  518. } else {
  519. if (query.modelType) {
  520. if (query.modelType === "practise") {
  521. headTopData.handleChangeModeType("practise");
  522. } else if (query.modelType === "evaluating") {
  523. headTopData.handleChangeModeType("evaluating");
  524. }
  525. headTopData.showBack = false;
  526. } else {
  527. setTimeout(() => {
  528. headTopData.modeType = "init";
  529. }, 500);
  530. }
  531. }
  532. };
  533. /** 课件播放 */
  534. const changePlay = (res: any) => {
  535. // console.log('监听上课页面message',res)
  536. if (res?.data?.api === "setPlayState") {
  537. togglePlay("paused", true);
  538. }
  539. if(res?.data?.api === 'togglePlayState') {
  540. // if(state.playState === "play") {
  541. // togglePlay("paused");
  542. // }
  543. // if(state.playState === 'paused') {
  544. // togglePlay("play");
  545. // }
  546. console.log('togglePlayState', state.playState)
  547. togglePlay(state.playState === "play" ? "paused" : "play");
  548. }
  549. // 上课页面,按钮方向
  550. if (res?.data?.api === "imagePos") {
  551. if (res?.data.data) {
  552. state.playBtnDirection = res.data.data === "right" ? "right" : "left";
  553. // if (state.fingeringInfo.direction === "vertical" && state.setting.displayFingering) {
  554. // state.musicScoreBtnDirection = state.playBtnDirection === 'right' ? 'left' : 'right';
  555. // } else {
  556. // state.musicScoreBtnDirection = state.playBtnDirection;
  557. // }
  558. state.musicScoreBtnDirection = state.playBtnDirection;
  559. }
  560. }
  561. };
  562. const parentClassName = "settingBoxClass_drag";
  563. const userId = storeData.user?.id ? String(storeData.user?.id) : "";
  564. const positionInfo =
  565. state.platform !== IPlatform.PC
  566. ? {
  567. styleDrag: { value: null },
  568. }
  569. : useDrag([`${parentClassName} .top_draging`, `${parentClassName} .bom_drag`], parentClassName, toRef(headTopData, "settingMode"), userId);
  570. const speedClassName = "speedBoxClass_drag";
  571. const speedInfo =
  572. state.platform !== IPlatform.PC
  573. ? {
  574. styleDrag: { value: null },
  575. }
  576. : useDrag([`${speedClassName} .top_draging`, `${speedClassName} .bom_drag`], speedClassName, toRef(headData, "speedShow"), userId);
  577. onMounted(() => {
  578. getQueryModelSetModelType();
  579. window.addEventListener("message", changePlay);
  580. if (state.platform === IPlatform.PC) {
  581. showGuide.value = true;
  582. } else {
  583. showStudentGuide.value = true;
  584. }
  585. if (query.showWebGuide === "false") {
  586. showWebGuide.value = false;
  587. }
  588. document.addEventListener("keydown", (e: KeyboardEvent) => {
  589. if (e.code === "Tab") {
  590. e.stopPropagation();
  591. e.preventDefault();
  592. // onStartPlayState();
  593. togglePlay(state.playState === "play" ? "paused" : "play");
  594. }
  595. });
  596. });
  597. onUnmounted(() => {
  598. window.removeEventListener("message", changePlay);
  599. });
  600. const noticeBarWidth = ref<number>();
  601. watch(
  602. () => smoothAnimationState.isShow.value,
  603. () => {
  604. // NoticeBar能不能滚动
  605. if ((smoothAnimationState.isShow.value || state.isCombineRender) && isMusicList.value) {
  606. nextTick(() => {
  607. const widthCon = (document.querySelector("#noticeBarRollDom .van-notice-bar__content") as any)?.offsetWidth || undefined;
  608. noticeBarWidth.value = widthCon;
  609. });
  610. }
  611. },
  612. { immediate: true }
  613. );
  614. // 设置改变触发
  615. watch(state.setting, () => {
  616. console.log(state.setting, "state.setting");
  617. store.set("musicscoresetting", state.setting);
  618. });
  619. // 获取引导页信息
  620. const getAllGuidance = async () => {
  621. let guideInfo: any = null;
  622. try {
  623. const res = await getGuidance({ guideTag: "guideInfo" });
  624. if (res.data) {
  625. guideInfo = JSON.parse(res.data?.guideValue) || null;
  626. } else {
  627. guideInfo = {};
  628. }
  629. state.guideInfo = guideInfo;
  630. } catch (e) {
  631. console.log(e);
  632. }
  633. };
  634. getAllGuidance();
  635. // 完成拖动弹窗引导页
  636. const handleGuide = async () => {
  637. state.guideInfo.teacherDrag = true;
  638. try {
  639. const res = await setGuidance({ guideTag: "guideInfo", guideValue: JSON.stringify(state.guideInfo) });
  640. } catch (e) {
  641. console.log(e);
  642. }
  643. };
  644. return () => (
  645. <>
  646. <div
  647. class={[styles.headerTop, state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.headerTopRight : ""]}
  648. onClick={(e: Event) => {
  649. e.stopPropagation();
  650. if (state.platform === IPlatform.PC) {
  651. // 显示隐藏菜单
  652. window.parent.postMessage(
  653. {
  654. api: "onAttendToggleMenu",
  655. },
  656. "*"
  657. );
  658. }
  659. }}
  660. >
  661. {/* 返回和标题 */}
  662. {!(state.playState == "play" || followData.start || evaluatingData.startBegin) && (
  663. <div id="noticeBarRollDom" class={styles.headTopLeftBox}>
  664. {
  665. !query.isMove && <img src={iconBack} class={["headTopBackBtn", styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
  666. }
  667. {smoothAnimationState.isShow.value || state.isCombineRender ? (
  668. <div
  669. style={
  670. noticeBarWidth.value
  671. ? {
  672. "--noticeBarWidth": noticeBarWidth.value + "px",
  673. }
  674. : {}
  675. }
  676. class={[styles.title, state.isCbsView && styles.blackTitle, "headeTopTitleBtn"]}
  677. onClick={() => {
  678. isMusicList.value && (musicListShow.value = true);
  679. }}
  680. >
  681. {isMusicList.value && <div class={[styles.symbolNote, "driver-8"]}></div>}
  682. <NoticeBar text={state.examSongName} background="none" />
  683. </div>
  684. ) : (
  685. isMusicList.value && (
  686. <img
  687. src={listImg}
  688. class={[styles.img, styles.listImg, "driver-8"]}
  689. onClick={() => {
  690. musicListShow.value = true;
  691. }}
  692. />
  693. )
  694. )}
  695. </div>
  696. )}
  697. {/* 模式切换 */}
  698. {
  699. <div
  700. id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
  701. style={{ display: toggleBtn.value.display ? "" : "none" }}
  702. class={["driver-9", styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]}
  703. onClick={() => {
  704. headTopData.oldModeType = state.modeType;
  705. handleRessetState();
  706. headTopData.modeType = "init";
  707. }}
  708. >
  709. <img class={styles.img} src={iconMode} />
  710. <div class={styles.title}>{state.modeType === "practise" ? "练习模式" : state.modeType === "follow" ? "跟练模式" : state.modeType === "evaluating" ? "评测模式" : ""}</div>
  711. </div>
  712. }
  713. {/* 模式提醒 */}
  714. {state.modeType === "practise" && (
  715. <div class={[styles.modeWarn, "practiseModeWarn", state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.modeWarnRight : ""]}>
  716. <img src={state.playType === "play" ? headImg("perform1.png") : headImg("sing1.png")} />
  717. <div>{state.playType === "play" ? "演奏场景" : "演唱场景"}</div>
  718. </div>
  719. )}
  720. {/* 功能按钮 */}
  721. <div
  722. class={[styles.headRight]}
  723. onClick={(e: Event) => {
  724. e.stopPropagation();
  725. }}
  726. >
  727. {/* 一行谱模式,暂不支持节拍指针 */}
  728. {/* {!state.isSingleLine ? (
  729. <div
  730. class={[styles.btn, state.platform === IPlatform.PC ? styles.pcBtn : ""]}
  731. onClick={() => {
  732. // 切换光标模式
  733. let mode = metronomeData.cursorMode;
  734. if (["follow"].includes(state.modeType)) {
  735. mode = metronomeData.cursorMode === 1 ? 3 : 1;
  736. } else {
  737. mode = metronomeData.cursorMode === 3 ? 1 : metronomeData.cursorMode + 1;
  738. }
  739. metronomeData.cursorMode = mode;
  740. }}
  741. >
  742. <img class={styles.iconBtn} src={headImg(metronomeData.cursorMode === 1 ? "cursor-icon-1.svg" : metronomeData.cursorMode === 2 ? "cursor-icon-2.svg" : metronomeData.cursorMode === 3 ? "cursor-icon-3.svg" : "")} />
  743. <span class={styles.iconContent}>
  744. {metronomeData.cursorMode === 1 ? "音符指针" : metronomeData.cursorMode === 2 ? "节拍指针" : metronomeData.cursorMode === 3 ? "关闭指针" : ""}
  745. {metronomeData.cursorTips && (
  746. <>
  747. <i class={styles.arrowIcon}></i>
  748. <div class={[styles["botton-tips"], metronomeData.cursorMode === 3 ? styles.tipSpec : ""]}>{metronomeData.cursorTips}</div>
  749. </>
  750. )}
  751. </span>
  752. </div>
  753. ) : null} */}
  754. <div
  755. style={{ display: playTypeBtn.value.display ? "" : "none" }}
  756. class={["driver-2", styles.btn, playTypeBtn.value.disabled && styles.disabled, styles.playType]}
  757. onClick={() => {
  758. const oldPlayType = state.playType;
  759. headTopData.oldPlayType = oldPlayType;
  760. const oldPlaySource = state.playSource;
  761. if (state.playType === "play") {
  762. state.playType = "sing";
  763. state.playSource = state.fanSong ? "music" : state.banSong ? "background" : "mingSong";
  764. } else {
  765. state.playType = "play";
  766. state.playSource = state.music ? "music" : "background";
  767. }
  768. isClickMode = true;
  769. // 有指法并且显示指法的时候 切换到演唱模式 需要影藏指法
  770. let isRefresh = false;
  771. if (state.isShowFingering && state.fingeringInfo.name && (state.setting.displayFingering || displayFingeringCache)) {
  772. if (state.playType === "sing") {
  773. state.setting.displayFingering = false;
  774. displayFingeringCache = true;
  775. } else {
  776. state.setting.displayFingering = displayFingeringCache;
  777. displayFingeringCache = false;
  778. }
  779. // 如果是竖屏指法和一行谱的时候 改变指法值的时候state 会调用刷新 refreshMusicSvg 所以下面不调用
  780. if (state.fingeringInfo.direction === "vertical" && !state.isSingleLine) {
  781. isRefresh = true;
  782. }
  783. }
  784. // 有歌词的时候,切换播放模式,需要重新渲染谱面 指法不刷新谱面的时候
  785. if (state.xmlHasLyric && !isRefresh) {
  786. refreshMusicSvg();
  787. } else if (!isRefresh) {
  788. handlerModeChange(oldPlayType, oldPlaySource, true);
  789. }
  790. }}
  791. >
  792. <img style={{ display: state.playType === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg(`perform.png`)} />
  793. <img style={{ display: state.playType === "play" ? "none" : "" }} class={styles.iconBtn} src={headImg(`sing.png`)} />
  794. <span>{state.playType === "play" ? "演奏" : "演唱"}</span>
  795. </div>
  796. <div
  797. id={state.platform === IPlatform.PC ? "teacherTop-1" : "studnetT-1"}
  798. style={{ display: originBtn.value.display ? "" : "none" }}
  799. class={["driver-3", styles.btn, originBtn.value.disabled && styles.disabled, state.playType === "play" ? styles.playSource : styles.songSource]}
  800. onClick={() => {
  801. const oldPlayType = state.playType;
  802. const oldPlaySource = state.playSource;
  803. if (state.playType === "play") {
  804. state.playSource = state.playSource === "music" ? "background" : "music";
  805. } else {
  806. if (state.playSource === "music") {
  807. state.playSource = state.banSong ? "background" : "mingSong";
  808. } else if (state.playSource === "background") {
  809. state.playSource = state.mingSong ? "mingSong" : "music";
  810. } else {
  811. state.playSource = state.fanSong ? "music" : "background";
  812. }
  813. }
  814. handlerModeChange(oldPlayType, oldPlaySource);
  815. showToast({
  816. message: state.playType === "play" ? (state.playSource === "music" ? "已切换为原声" : "已切换为伴奏") : state.playSource === "music" ? "已切换为范唱" : state.playSource === "background" ? "已切换为伴唱" : "已切换为唱名",
  817. position: "top",
  818. className: "selectionToast",
  819. });
  820. }}
  821. >
  822. <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={state.playType === "play" ? headImg(`music.png`) : headImg(`music1.png`)} />
  823. <img style={{ display: state.playSource === "background" ? "" : "none" }} class={styles.iconBtn} src={state.playType === "play" ? headImg(`background.png`) : headImg(`background1.png`)} />
  824. <img style={{ display: state.playSource === "mingSong" ? "" : "none" }} class={styles.iconBtn} src={headImg(`mingsong.png`)} />
  825. <span>{state.playSource === "music" ? (state.playType === "play" ? "原声" : "范唱") : state.playSource === "background" ? (state.playType === "play" ? "伴奏" : "伴唱") : "唱名"}</span>
  826. </div>
  827. <div
  828. id={state.platform === IPlatform.PC ? "teacherTop-2" : "studnetT-2"}
  829. style={{ display: selectBtn.value.display ? "" : "none" }}
  830. class={["driver-4", styles.btn, selectBtn.value.disabled && styles.disabled, styles.section, state.sectionStatus && styles.isSection]}
  831. onClick={() => handleChangeSection()}
  832. >
  833. <img style={{ display: state.section.length === 0 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section0.png`)} />
  834. <img style={{ display: state.section.length === 1 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section1.png`)} />
  835. <img style={{ display: state.section.length === 2 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section2.png`)} />
  836. <span>选段</span>
  837. </div>
  838. {
  839. <>
  840. <div
  841. style={{ display: metronomeBtn.value.display ? "" : "none" }}
  842. class={["driver-5", styles.btn, styles.metronomeBtn, metronomeBtn.value.disabled && styles.disabled, headData.speedShow && styles.isSpeed, styles.speed]}
  843. onClick={async () => {
  844. headData.speedShow = !headData.speedShow;
  845. }}
  846. >
  847. <img style={{ display: metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickon.png")} />
  848. <img style={{ display: !metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickoff.png")} />
  849. <span style={{ whiteSpace: "nowrap" }}>节拍</span>
  850. <div class={styles.speedCon}>
  851. <img src={headImg("speed.png")} />
  852. <div>{Math.floor(state.speed)}</div>
  853. </div>
  854. </div>
  855. {
  856. <Popup v-model:show={headData.speedShow} class="popup-custom van-scale center-closeBtn speedBoxClass_drag" transition="van-scale" teleport="body" style={speedInfo.styleDrag.value} overlay-style={{ background: "rgba(0, 0, 0, 0.3)" }}>
  857. <Speed />
  858. {state.platform === IPlatform.PC && <Dragbom showGuide={!state.guideInfo?.teacherDrag} onGuideDone={handleGuide} />}
  859. </Popup>
  860. }
  861. </>
  862. }
  863. {/* {state.enableNotation ? (
  864. <Popover trigger="manual" v-model:show={headData.musicTypeShow} class={state.platform === IPlatform.PC && styles.pcTransPop} placement={state.platform === IPlatform.PC ? "top-end" : "bottom-end"} overlay={false} offset={state.platform === IPlatform.PC ? [0, 40] : [0, 8]}>
  865. {{
  866. reference: () => (
  867. <div
  868. id={state.platform === IPlatform.PC ? "teacherTop-5" : "studnetT-5"}
  869. style={{ display: converBtn.value.display ? "" : "none" }}
  870. class={[styles.btn, converBtn.value.disabled && styles.disabled]}
  871. onClick={(e: Event) => {
  872. e.stopPropagation();
  873. headData.musicTypeShow = !headData.musicTypeShow;
  874. }}
  875. >
  876. <img class={styles.iconBtn} src={headImg("icon_zhuanpu.svg")} />
  877. <span>{state.musicRenderType === "staff" ? "转简谱" : "转五线谱"}</span>
  878. </div>
  879. ),
  880. default: () => <MusicType />,
  881. }}
  882. </Popover>
  883. ) : null} */}
  884. {state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert && (
  885. <div
  886. class={[styles.btn, state.playState === "play" && fingeringBtn.value.disabled && styles.disabled, toggleMusicSheet.show && styles.isMusicSheet, styles.musicSheet, "driver-10"]}
  887. onClick={() => {
  888. toggleMusicSheet.toggle(true);
  889. }}
  890. >
  891. <img class={styles.iconBtn} src={headImg(`shenggui.png`)} />
  892. <span>声部</span>
  893. </div>
  894. )}
  895. <div
  896. id={state.platform === IPlatform.PC ? "teacherTop-6" : "studnetT-6"}
  897. style={{ display: settingBtn.value.display ? "" : "none" }}
  898. class={["driver-6", styles.btn, settingBtn.value.disabled && styles.disabled, headTopData.settingMode && styles.isSettingMode, styles.settingMode]}
  899. onClick={() => (headTopData.settingMode = true)}
  900. >
  901. <img class={styles.iconBtn} src={headImg("icon_menu.png")} />
  902. <span>设置</span>
  903. </div>
  904. </div>
  905. </div>
  906. {/** 指法点击区域 */}
  907. {
  908. state.fingeringInfo.direction === "transverse" && state.setting.displayFingering ?
  909. <div class={styles.headerMid} onClick={() => {
  910. fingerRef.value?.doubeClick()
  911. }}></div> : null
  912. }
  913. {/* 播放按钮 */}
  914. <div
  915. id="studnetT-7"
  916. style={{ display: playBtn.value.display ? "" : "none" }}
  917. class={[
  918. // 引导使用的类
  919. "driver-1",
  920. styles.playBtn,
  921. playBtn.value.disabled && styles.disabled,
  922. state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.playLeftButton : state.platform === IPlatform.PC && state.musicScoreBtnDirection === "right" ? styles.playRightButton : "",
  923. ]}
  924. onClick={() => {
  925. // C调能播放唱名,非C调时,只有谱面类型是首调时,才能播放唱名
  926. if (!state.isCTone && state.playSource === 'mingSong') {
  927. const notPlayDesc = state.musicRenderType === EnumMusicRenderType.staff ? '该曲目的五线谱目前还不支持播放唱名' : state.musicRenderType === EnumMusicRenderType.fixedTone ? '该曲目的固定调目前还不支持播放唱名' : '';
  928. if (notPlayDesc) {
  929. showToast({
  930. message: notPlayDesc,
  931. position: "top",
  932. className: "selectionToast",
  933. });
  934. return
  935. }
  936. }
  937. togglePlay(state.playState === "play" ? "paused" : "play")
  938. }}
  939. >
  940. <div class={styles.btnWrap}>
  941. <img style={{ display: state.playState === "play" ? "none" : "" }} class={styles.iconBtn} src={headImg("icon_play.png")} />
  942. <img style={{ display: state.playState === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg("icon_pause.png")} />
  943. <Circle style={{ opacity: state.playState === "play" ? 1 : 0 }} class={styles.progress} stroke-width={60} stroke-linecap={"square"} currentRate={state.playProgress} rate={100} color="#FFED78" layer-color="rgba(0,0,0,0)" />
  944. </div>
  945. </div>
  946. {/* 重播按钮 */}
  947. <div
  948. id="tips-step-9"
  949. style={{ display: resetBtn.value.display ? "" : "none" }}
  950. class={[styles.resetBtn, resetBtn.value.disabled && styles.disabled, state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.pauseLeftButton : state.platform === IPlatform.PC && state.musicScoreBtnDirection === "right" ? styles.pauseRightButton : ""]}
  951. onClick={() => handleResetPlay()}
  952. >
  953. <img class={styles.iconBtn} src={headImg("icon_reset.png")} />
  954. </div>
  955. <Popup v-model:show={headTopData.settingMode} class="popup-custom van-scale center-closeBtn settingBoxClass_drag" transition="van-scale" teleport="body" style={positionInfo.styleDrag.value} overlay-style={{ background: "rgba(0, 0, 0, 0.3)" }}>
  956. <Settting />
  957. {state.platform === IPlatform.PC && <Dragbom showGuide={!state.guideInfo?.teacherDrag} onGuideDone={handleGuide} />}
  958. </Popup>
  959. {/* 模式切换 */}
  960. {/* <ModeTypeMode /> */}
  961. <ModeView></ModeView>
  962. {/* isAllBtns */}
  963. {/* {isAllBtns.value && !query.isCbs && showGuideIndex.value && <TeacherTop></TeacherTop>}
  964. {isAllBtnsStudent.value && !query.isCbs && showGuideIndex.value && <StudentTop></StudentTop>} */}
  965. {/* 练习模式功能引导 加载音频完成 不是会员 */}
  966. {state.modeType === "practise" && headTopData.modeType !== "init" && !query.isCbs && state.audioDone && !state.isLoading && !state.isVip && showWebGuide.value && (
  967. <PractiseDriver
  968. statusAll={{
  969. playBtnStatus: playBtn.value.display,
  970. subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
  971. modelTypeStatus: toggleBtn.value.display,
  972. playType: playTypeBtn.value.display,
  973. originPlayType: state.playType === "play" ? true : false,
  974. originBtnStatus: originBtn.value.display,
  975. backTitle: !(state.playState == "play" || followData.start || evaluatingData.startBegin) && isMusicList.value,
  976. titleType: smoothAnimationState.isShow.value ? "TEXT" : isMusicList.value ? "IMG" : "NONE",
  977. }}
  978. />
  979. )}
  980. {/* 跟练模式功能引导 加载音频完成 不是会员 */}
  981. {state.modeType === "follow" && headTopData.modeType !== "init" && !query.isCbs && state.audioDone && !state.isLoading && !state.isVip && showWebGuide.value && (
  982. <FollowDriver
  983. statusAll={{
  984. subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
  985. }}
  986. />
  987. )}
  988. {/* 评测模式功能引导 加载音频完成 不是会员 */}
  989. {state.modeType === "evaluating" && headTopData.modeType !== "init" && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isLoading && !state.isVip && evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && showWebGuide.value && (
  990. <EvaluatingDriver
  991. statusAll={{
  992. subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
  993. }}
  994. />
  995. )}
  996. </>
  997. );
  998. },
  999. });