index.tsx 45 KB

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