index.tsx 36 KB

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