index.tsx 43 KB

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