index.tsx 41 KB

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