index.tsx 34 KB

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