index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch } from "vue";
  2. import styles from "./index.module.less";
  3. import iconBack from "./image/icon-back.svg";
  4. import Title from "./title";
  5. import { headImg } from "./image";
  6. import { Badge, Circle, Popover, Popup, showConfirmDialog } from "vant";
  7. import Speed from "./speed";
  8. import { evaluatingData, handleStartEvaluat } from "/src/view/evaluating";
  9. import Settting from "./settting";
  10. import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay } from "/src/state";
  11. import { getAudioCurrentTime } from "/src/view/audio-list";
  12. import { followData, toggleFollow } from "/src/view/follow-practice";
  13. import { api_back } from "/src/helpers/communication";
  14. import MusicType from "./music-type";
  15. import ModeTypeMode from "../component/mode-type-mode";
  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. /** 头部数据和方法 */
  25. export const headTopData = reactive({
  26. /** 模式 */
  27. modeType: "" as "init" | "show",
  28. /** 显示返回按钮 */
  29. showBack: true,
  30. /** 设置弹窗 */
  31. settingMode: false,
  32. /** 切换模式 */
  33. handleChangeModeType(value: "practise" | "follow" | "evaluating") {
  34. if (value === "evaluating") {
  35. // 如果是pc端, 评测模式暂不可用
  36. if (state.platform === IPlatform.PC) {
  37. showConfirmDialog({
  38. className: "modalTip",
  39. title: "温馨提示",
  40. message: "该功能暂未开放,敬请期待!",
  41. showCancelButton: false,
  42. });
  43. return;
  44. }
  45. handleStartEvaluat();
  46. } else if (value === "follow") {
  47. toggleFollow();
  48. }
  49. headTopData.modeType = "show";
  50. },
  51. });
  52. export const headData = reactive({
  53. speedShow: false,
  54. musicTypeShow: false,
  55. });
  56. export default defineComponent({
  57. name: "header-top",
  58. emits: ["close"],
  59. setup(props, { emit }) {
  60. const query = getQuery();
  61. // 是否显示引导
  62. const showGuide = ref(false);
  63. const showStudentGuide = ref(false);
  64. /** 设置按钮 */
  65. const settingBtn = computed(() => {
  66. // 音频播放中 禁用
  67. if (state.playState === "play") return { display: true, disabled: true };
  68. // 评测开始 禁用, 跟练开始 禁用
  69. if (evaluatingData.startBegin || followData.start) return { display: true, disabled: true };
  70. return {
  71. display: true,
  72. disabled: false,
  73. };
  74. });
  75. /** 转谱按钮 */
  76. const converBtn = computed(() => {
  77. // 音频播放中 禁用
  78. if (state.playState === "play") return { display: true, disabled: true };
  79. // 评测开始 禁用
  80. if (evaluatingData.startBegin || followData.start) return { display: true, disabled: true };
  81. return {
  82. disabled: false,
  83. display: true,
  84. };
  85. });
  86. /** 速度按钮 */
  87. const speedBtn = computed(() => {
  88. // 选择模式, 跟练模式 不显示
  89. if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: true };
  90. // 评测模式, 音频播放中 禁用
  91. if (state.modeType === "evaluating" || state.playState === "play") return { display: true, disabled: true };
  92. return {
  93. disabled: false,
  94. display: true,
  95. };
  96. });
  97. /** 指法按钮 */
  98. const fingeringBtn = computed(() => {
  99. // 没有指法 选择模式 评测模式 跟练模式 不显示
  100. if (headTopData.modeType !== "show" || !state.fingeringInfo.name || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  101. // 音频播放中 禁用
  102. if (state.playState === "play") return { display: true, disabled: true };
  103. return {
  104. disabled: false,
  105. display: true,
  106. };
  107. });
  108. /** 摄像头按钮 */
  109. const cameraBtn = computed(() => {
  110. // 选择模式 不显示
  111. if (headTopData.modeType !== "show" || state.modeType !== "evaluating") return { display: false, disabled: true };
  112. // 音频播放中 禁用
  113. if (state.playState === "play") return { display: true, disabled: true };
  114. return {
  115. disabled: false,
  116. display: true,
  117. };
  118. });
  119. /** 选段按钮 */
  120. const selectBtn = computed(() => {
  121. // 选择模式 不显示
  122. if (headTopData.modeType !== "show" || ["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  123. // 音频播放中 禁用
  124. if (state.playState === "play") return { display: true, disabled: true };
  125. return {
  126. disabled: false,
  127. display: true,
  128. };
  129. });
  130. /** 原声按钮 */
  131. const originBtn = computed(() => {
  132. // 选择模式,跟练模式 不显示
  133. if (headTopData.modeType !== "show" || state.modeType === "follow") return { display: false, disabled: false };
  134. // 评测开始 禁用
  135. if (state.modeType === "evaluating") return { display: false, disabled: true };
  136. // 原声, 伴奏 少一个,就不能切换
  137. if (!state.music || !state.accompany) return { display: true, disabled: true };
  138. return {
  139. disabled: false,
  140. display: true,
  141. };
  142. });
  143. /** 模式切换按钮 */
  144. const toggleBtn = computed(() => {
  145. // 选择模式, url设置模式 不显示
  146. if (headTopData.modeType !== "show" || !headTopData.showBack) return { display: false, disabled: false };
  147. // 跟练开始, 评测开始 禁用
  148. if (followData.start || evaluatingData.startBegin) return { display: true, disabled: true };
  149. return {
  150. display: true,
  151. disabled: false,
  152. };
  153. });
  154. /** 播放按钮 */
  155. const playBtn = computed(() => {
  156. // 选择模式 不显示
  157. if (headTopData.modeType !== "show") return { display: false, disabled: false };
  158. // 评测模式 不显示,跟练模式 不显示
  159. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  160. return {
  161. display: true,
  162. disabled: false,
  163. };
  164. });
  165. /** 重播按钮 */
  166. const resetBtn = computed(() => {
  167. // 选择模式 不显示
  168. if (headTopData.modeType !== "show") return { display: false, disabled: false };
  169. // 评测模式 不显示,跟练模式 不显示
  170. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  171. // 播放状态 不显示
  172. if (state.playState === "play") return { display: false, disabled: true };
  173. // 播放进度为0 不显示
  174. const currentTime = getAudioCurrentTime();
  175. if (!currentTime) return { display: false, disabled: true };
  176. return {
  177. display: true,
  178. disabled: false,
  179. };
  180. });
  181. const isAllBtns = computed(() => {
  182. const flag = converBtn.value.display && speedBtn.value.display && selectBtn.value.display && originBtn.value.display && toggleBtn.value.display && showGuide.value;
  183. return flag;
  184. });
  185. const isAllBtnsStudent = computed(() => {
  186. const flag = converBtn.value.display && speedBtn.value.display && selectBtn.value.display && originBtn.value.display && toggleBtn.value.display && showStudentGuide.value;
  187. return flag;
  188. });
  189. const browInfo = browser();
  190. /** 返回 */
  191. const handleBack = () => {
  192. HANDLE_WORK_ADD();
  193. // 不在APP中,
  194. if (!storeData.isApp) {
  195. window.close();
  196. return;
  197. }
  198. if ((browInfo.iPhone || browInfo.ios) && query.workRecord) {
  199. setTimeout(() => {
  200. api_back();
  201. }, 550);
  202. return;
  203. }
  204. api_back();
  205. };
  206. /** 根据参数设置模式 */
  207. const getQueryModelSetModelType = () => {
  208. /** 作业模式 start, 如果为作业模式不处理,让作业模块处理 */
  209. if (query.workRecord) {
  210. return;
  211. }
  212. /** 作业模式 end */
  213. if (query.modelType) {
  214. if (query.modelType === "practise") {
  215. headTopData.handleChangeModeType("practise");
  216. } else if (query.modelType === "evaluating") {
  217. headTopData.handleChangeModeType("evaluating");
  218. }
  219. headTopData.showBack = false;
  220. } else {
  221. setTimeout(() => {
  222. headTopData.modeType = "init";
  223. }, 500);
  224. }
  225. };
  226. /** 课件播放 */
  227. const changePlay = (res: any) => {
  228. if (res?.data?.api === "setPlayState") {
  229. togglePlay("paused");
  230. }
  231. // 菜单状态
  232. if ((state.platform === IPlatform.PC && res?.data?.api) === "attendClassBarStatus") {
  233. state.attendHideMenu = res?.data?.hideMenu;
  234. }
  235. };
  236. onMounted(() => {
  237. getQueryModelSetModelType();
  238. window.addEventListener("message", changePlay);
  239. if (state.platform === IPlatform.PC) {
  240. showGuide.value = true;
  241. } else {
  242. showStudentGuide.value = true;
  243. }
  244. });
  245. onUnmounted(() => {
  246. window.removeEventListener("message", changePlay);
  247. });
  248. // 设置改变触发
  249. watch(state.setting, () => {
  250. console.log(state.setting, "state.setting");
  251. store.set("musicscoresetting", state.setting);
  252. });
  253. return () => (
  254. <>
  255. <div
  256. class={[styles.headerTop, state.platform === IPlatform.PC && styles.headRightTop, state.platform === IPlatform.PC && !state.attendHideMenu && styles.headRightTopHide]}
  257. onClick={(e: Event) => {
  258. e.stopPropagation();
  259. if (state.platform === IPlatform.PC) {
  260. // 显示隐藏菜单
  261. window.parent.postMessage(
  262. {
  263. api: "onAttendToggleMenu",
  264. },
  265. "*"
  266. );
  267. }
  268. }}
  269. >
  270. <div class={[styles.back, "headTopBackBtn", !headTopData.showBack && styles.hidenBack]} onClick={handleBack}>
  271. <img src={iconBack} />
  272. </div>
  273. {query.iscurseplay === "play" ? null : <Title class="pcTitle" text={state.examSongName} rightView={false} />}
  274. <div
  275. class={[styles.headRight]}
  276. onClick={(e: Event) => {
  277. e.stopPropagation();
  278. }}
  279. >
  280. <div
  281. id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
  282. style={{ display: toggleBtn.value.display ? "" : "none" }}
  283. class={[styles.btn, toggleBtn.value.disabled && styles.disabled]}
  284. onClick={() => {
  285. handleRessetState();
  286. headTopData.modeType = "init";
  287. }}
  288. >
  289. <img class={styles.iconBtn} src={headImg(`modeType.svg`)} />
  290. <span>模式</span>
  291. </div>
  292. <div
  293. id={state.platform === IPlatform.PC ? "teacherTop-1" : "studnetT-1"}
  294. style={{ display: originBtn.value.display ? "" : "none" }}
  295. class={[styles.btn, originBtn.value.disabled && styles.disabled]}
  296. onClick={() => {
  297. state.playSource = state.playSource === "music" ? "background" : "music";
  298. }}
  299. >
  300. <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={headImg(`music.svg`)} />
  301. <img style={{ display: state.playSource === "music" ? "none" : "" }} class={styles.iconBtn} src={headImg(`background.svg`)} />
  302. <span>{state.playSource === "music" ? "原声" : "伴奏"}</span>
  303. </div>
  304. <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()}>
  305. <img style={{ display: state.section.length === 0 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section0.svg`)} />
  306. <img style={{ display: state.section.length === 1 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section1.svg`)} />
  307. <img style={{ display: state.section.length === 2 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section2.svg`)} />
  308. <span>选段</span>
  309. </div>
  310. <div
  311. id={state.platform === IPlatform.PC ? "teacherTop-3" : "studnetT-3"}
  312. style={{ display: fingeringBtn.value.display ? "" : "none" }}
  313. class={[styles.btn, fingeringBtn.value.disabled && styles.disabled]}
  314. onClick={() => {
  315. state.setting.displayFingering = !state.setting.displayFingering;
  316. }}
  317. >
  318. <img style={{ display: state.setting.displayFingering ? "" : "none" }} class={styles.iconBtn} src={headImg(`icon_evaluatingOn.svg`)} />
  319. <img style={{ display: state.setting.displayFingering ? "none" : "" }} class={styles.iconBtn} src={headImg(`icon_evaluatingOff.svg`)} />
  320. <span>指法</span>
  321. </div>
  322. <Popover trigger="manual" v-model:show={headData.speedShow} placement="bottom" overlay={false}>
  323. {{
  324. reference: () => (
  325. <div
  326. id={state.platform === IPlatform.PC ? "teacherTop-4" : "studnetT-4"}
  327. style={{ display: speedBtn.value.display ? "" : "none" }}
  328. class={[styles.btn, speedBtn.value.disabled && styles.disabled]}
  329. onClick={(e: Event) => {
  330. e.stopPropagation();
  331. headData.speedShow = !headData.speedShow;
  332. }}
  333. >
  334. <Badge class={styles.badge} content={state.speed}>
  335. <img class={styles.iconBtn} src={headImg("icon_speed.svg")} />
  336. </Badge>
  337. <span>速度</span>
  338. </div>
  339. ),
  340. default: () => <Speed />,
  341. }}
  342. </Popover>
  343. <Popover trigger="manual" v-model:show={headData.musicTypeShow} placement="bottom-end" overlay={false}>
  344. {{
  345. reference: () => (
  346. <div
  347. id={state.platform === IPlatform.PC ? "teacherTop-5" : "studnetT-5"}
  348. style={{ display: converBtn.value.display ? "" : "none" }}
  349. class={[styles.btn, converBtn.value.disabled && styles.disabled]}
  350. onClick={(e: Event) => {
  351. e.stopPropagation();
  352. headData.musicTypeShow = !headData.musicTypeShow;
  353. }}
  354. >
  355. <img class={styles.iconBtn} src={headImg("icon_zhuanpu.svg")} />
  356. <span>{state.musicRenderType === "staff" ? "转谱" : "转谱"}</span>
  357. </div>
  358. ),
  359. default: () => <MusicType />,
  360. }}
  361. </Popover>
  362. <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)}>
  363. <img class={styles.iconBtn} src={headImg("icon_menu.svg")} />
  364. <span>设置</span>
  365. </div>
  366. </div>
  367. </div>
  368. {/* 播放按钮 */}
  369. <div
  370. id="studnetT-7"
  371. style={{ display: playBtn.value.display ? "" : "none" }}
  372. class={[styles.btn, styles.playBtn, playBtn.value.disabled && styles.disabled, state.platform === IPlatform.PC && styles.playButton, state.platform === IPlatform.PC && !state.attendHideMenu && styles.playButtonHide]}
  373. onClick={() => togglePlay()}
  374. >
  375. <div class={styles.btnWrap}>
  376. <img style={{ display: state.playState === "play" ? "none" : "" }} class={styles.iconBtn} src={headImg("icon_play.svg")} />
  377. <img style={{ display: state.playState === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg("icon_pause.svg")} />
  378. <Circle style={{ opacity: state.playState === "play" ? 1 : 0 }} class={styles.progress} stroke-width={80} currentRate={state.playProgress} rate={100} color="#FFC830" />
  379. </div>
  380. </div>
  381. {/* 重播按钮 */}
  382. <div
  383. id="tips-step-9"
  384. style={{ display: resetBtn.value.display ? "" : "none" }}
  385. class={[styles.btn, styles.resetBtn, resetBtn.value.disabled && styles.disabled, state.platform === IPlatform.PC && styles.pauseButton, state.platform === IPlatform.PC && !state.attendHideMenu && styles.playButtonHide]}
  386. onClick={() => handleResetPlay()}
  387. >
  388. <img class={styles.iconBtn} src={headImg("icon_resetbtn.svg")} />
  389. </div>
  390. <Popup v-model:show={headTopData.settingMode} class="popup-custom van-scale center-closeBtn" transition="van-scale" teleport="body" closeable>
  391. <Settting />
  392. </Popup>
  393. {/* 模式切换 */}
  394. <ModeTypeMode />
  395. {/* isAllBtns */}
  396. {isAllBtns.value && <TeacherTop></TeacherTop>}
  397. {isAllBtnsStudent.value && <StudentTop></StudentTop>}
  398. </>
  399. );
  400. },
  401. });