index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import { computed, defineComponent, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
  2. import styles from "./index.module.less";
  3. import store from "store";
  4. import Title from "./title";
  5. import icons from "./image/headerTop.json";
  6. import { Badge, Circle, Popover, Popup } from "vant";
  7. import { metronomeData } from "../../helpers/metronome";
  8. import Speed from "./speed";
  9. import { evaluatingData, handleEndBegin, handleStartEvaluat } from "/src/view/evaluating";
  10. import Settting from "./settting";
  11. import ModeTypeMode from "./mode-type-mode";
  12. import state, { handleChangeSection, handleResetPlay, handleRessetState, togglePlay } from "/src/state";
  13. import { getAudioCurrentTime } from "/src/view/audio-list";
  14. import { followData, toggleFollow } from "/src/view/follow-practice";
  15. import { api_back, api_suspendPlay } from "/src/helpers/communication";
  16. import MusicType from "./music-type";
  17. import { handleNoEndExit } from "../custom-plugins/recording-time";
  18. import { handle_stopFollow } from "../follow-model";
  19. import Camera from "./camera";
  20. import iconCamera from './image/icon-camera-off.png'
  21. import iconCameraOn from './image/icon-camera-on.png'
  22. export const headData = reactive({
  23. speedShow: false,
  24. musicTypeShow: false,
  25. modeMode: true, // 模式弹框
  26. settingMode: false, // 设置弹框
  27. cameraShow: false, // 摄像头
  28. });
  29. /** 关闭模式选择 */
  30. export const handleCloseModeMode = (type = false) => {
  31. headData.modeMode = type;
  32. };
  33. export default defineComponent({
  34. name: "header-top",
  35. setup() {
  36. /** 切换模式 */
  37. const handleChangeModeType = (value: "practise" | "follow" | "evaluating") => {
  38. if (value === "evaluating") {
  39. handleStartEvaluat();
  40. } else if (value === "follow") {
  41. toggleFollow();
  42. }
  43. headData.modeMode = false;
  44. };
  45. /** 设置按钮 */
  46. const settingBtn = computed(() => {
  47. // 音频播放中 禁用
  48. if (state.playState === "play") return { display: true, disabled: true };
  49. // 评测开始 禁用, 跟练开始 禁用
  50. if (evaluatingData.startBegin || followData.start) return { display: true, disabled: true };
  51. return {
  52. display: true,
  53. disabled: false,
  54. };
  55. });
  56. /** 转谱按钮 */
  57. const converBtn = computed(() => {
  58. // 音频播放中 禁用
  59. if (state.playState === "play") return { display: true, disabled: true };
  60. // 评测开始 禁用
  61. if (evaluatingData.startBegin || followData.start) return { display: true, disabled: true };
  62. return {
  63. disabled: false,
  64. display: true,
  65. };
  66. });
  67. /** 速度按钮 */
  68. const speedBtn = computed(() => {
  69. // 选择模式 不显示
  70. if (headData.modeMode) return { display: false, disabled: false };
  71. // 音频播放中 禁用
  72. if (state.playState === "play") return { display: true, disabled: true };
  73. // 评测模式 禁用
  74. if (state.modeType === "evaluating") return { display: true, disabled: true };
  75. // 跟练模式 不显示
  76. if (state.modeType === "follow") return { display: false, disabled: true };
  77. return {
  78. disabled: false,
  79. display: true,
  80. };
  81. });
  82. /** 指法按钮 */
  83. const fingeringBtn = computed(() => {
  84. // 没有指法 不显示
  85. if (!state.fingeringInfo.name) return { display: false, disabled: true };
  86. // 选择模式 不显示
  87. if (headData.modeMode) return { display: false, disabled: false };
  88. // 音频播放中 禁用
  89. if (state.playState === "play") return { display: true, disabled: true };
  90. // 评测模式 不显示,跟练模式 不显示
  91. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  92. return {
  93. disabled: false,
  94. display: true,
  95. };
  96. });
  97. /** 摄像头按钮 */
  98. const cameraBtn = computed(() => {
  99. // 选择模式 不显示
  100. if (headData.modeMode) return { display: false, disabled: false };
  101. // 音频播放中 禁用
  102. if (state.playState === "play") return { display: true, disabled: true };
  103. // 评测模式 不显示,跟练模式 不显示
  104. if (state.modeType !== 'evaluating') return { display: false, disabled: true };
  105. return {
  106. disabled: false,
  107. display: true,
  108. };
  109. });
  110. /** 选段按钮 */
  111. const selectBtn = computed(() => {
  112. // 选择模式 不显示
  113. if (headData.modeMode) return { display: false, disabled: false };
  114. // 音频播放中 禁用
  115. if (state.playState === "play") return { display: true, disabled: true };
  116. // 评测模式 不显示,跟练模式 不显示
  117. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  118. return {
  119. disabled: false,
  120. display: true,
  121. };
  122. });
  123. /** 原声按钮 */
  124. const originBtn = computed(() => {
  125. // 选择模式 不显示
  126. if (headData.modeMode) return { display: false, disabled: false };
  127. // 跟练模式 不显示
  128. if (state.modeType === "follow") return { display: false, disabled: true };
  129. // 评测开始 禁用
  130. if (state.modeType === "evaluating") return { display: false, disabled: true };
  131. // 原声, 伴奏 少一个,就不能切换
  132. if (!state.music || !state.accompany) return { display: true, disabled: true };
  133. return {
  134. disabled: false,
  135. display: true,
  136. };
  137. });
  138. /** 模式切换按钮 */
  139. const toggleBtn = computed(() => {
  140. // 选择模式 不显示
  141. if (headData.modeMode) return { display: false, disabled: false };
  142. // 跟练开始, 评测开始 禁用
  143. if (followData.start || evaluatingData.startBegin) return { display: true, disabled: true };
  144. return {
  145. display: true,
  146. disabled: false,
  147. };
  148. });
  149. /** 播放按钮 */
  150. const playBtn = computed(() => {
  151. // 选择模式 不显示
  152. if (headData.modeMode) return { display: false, disabled: false };
  153. // 评测模式 不显示,跟练模式 不显示
  154. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  155. return {
  156. display: true,
  157. disabled: false,
  158. };
  159. });
  160. /** 重播按钮 */
  161. const resetBtn = computed(() => {
  162. // 选择模式 不显示
  163. if (headData.modeMode) return { display: false, disabled: false };
  164. // 评测模式 不显示,跟练模式 不显示
  165. if (["evaluating", "follow"].includes(state.modeType)) return { display: false, disabled: true };
  166. // 播放状态 不显示
  167. if (state.playState === "play") return { display: false, disabled: true };
  168. // 播放进度为0 不显示
  169. const currentTime = getAudioCurrentTime();
  170. if (currentTime === 0) return { display: false, disabled: true };
  171. return {
  172. display: true,
  173. disabled: false,
  174. };
  175. });
  176. /** 返回 */
  177. const handleBack = () => {
  178. handleNoEndExit();
  179. api_back();
  180. };
  181. onMounted(() => {
  182. api_suspendPlay(() => {
  183. if (state.modeType === "practise") {
  184. togglePlay("paused");
  185. } else if (state.modeType === "evaluating") {
  186. handleEndBegin();
  187. } else if (state.modeType === "follow") {
  188. handle_stopFollow();
  189. }
  190. });
  191. });
  192. // 设置改变触发
  193. watch(state.setting, () => {
  194. store.set("musicscoresetting", state.setting);
  195. });
  196. return () => (
  197. <div class={styles.headerTop}>
  198. <div class={styles.back} onClick={handleBack}>
  199. <img src={icons["icon-back"]} />
  200. </div>
  201. <Title text={state.examSongName} rightView={false} />
  202. <div class={styles.headRight}>
  203. {/* 模式切换按钮 */}
  204. <div
  205. id="tips-step-0"
  206. style={{ display: toggleBtn.value.display ? "" : "none" }}
  207. class={[styles.btn, toggleBtn.value.disabled && styles.disabled]}
  208. onClick={() => {
  209. handleRessetState();
  210. headData.modeMode = true;
  211. }}
  212. >
  213. <img
  214. class={styles.iconBtn}
  215. src={state.modeType === "practise" ? icons.modelType : state.modeType === "evaluating" ? icons.modelType2 : icons.modelType1}
  216. />
  217. <span>模式</span>
  218. </div>
  219. {/* 原声按钮 */}
  220. <div
  221. id="tips-step-1"
  222. style={{ display: originBtn.value.display ? "" : "none" }}
  223. class={[styles.btn, originBtn.value.disabled && styles.disabled]}
  224. onClick={() => {
  225. state.playSource = state.playSource === "music" ? "background" : "music";
  226. }}
  227. >
  228. <img class={styles.iconBtn} src={state.playSource === "music" ? icons.music : icons.accompaniment} />
  229. <span>{state.playSource === "music" ? "原声" : "伴奏"}</span>
  230. </div>
  231. {/* 选段按钮 */}
  232. <div
  233. id="tips-step-2"
  234. style={{ display: selectBtn.value.display ? "" : "none" }}
  235. class={[styles.btn, selectBtn.value.disabled && styles.disabled]}
  236. onClick={() => handleChangeSection()}
  237. >
  238. <img class={styles.iconBtn} src={state.section.length === 0 ? icons.section : state.section.length === 1 ? icons.section1 : icons.section2} />
  239. <span>选段</span>
  240. </div>
  241. {/* 指法按钮 */}
  242. <div
  243. id="tips-step-3"
  244. style={{ display: fingeringBtn.value.display ? "" : "none" }}
  245. class={[styles.btn, fingeringBtn.value.disabled && styles.disabled]}
  246. onClick={() => {
  247. state.setting.displayFingering = !state.setting.displayFingering;
  248. }}
  249. >
  250. <img class={styles.iconBtn} src={state.setting.displayFingering ? icons.fingeringOn : icons.fingeringOff} />
  251. <span>指法</span>
  252. </div>
  253. {/* <div
  254. class={[styles.btn]}
  255. onClick={async () => {
  256. metronomeData.lineShow = !metronomeData.lineShow;
  257. }}
  258. >
  259. <img class={styles.iconBtn} src={headImg("iconStep.png")} />
  260. <span>{metronomeData.lineShow ? "高级" : "初级"}</span>
  261. </div> */}
  262. {/* <div
  263. class={styles.btn}
  264. onClick={async () => {
  265. metronomeData.disable = !metronomeData.disable;
  266. metronomeData.metro?.initPlayer();
  267. }}
  268. >
  269. <img style={{ display: metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickoff.png")} />
  270. <img style={{ display: !metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickon.png")} />
  271. <span style={{ whiteSpace: "nowrap" }}>节拍器</span>
  272. </div> */}
  273. {/* 摄像头按钮 */}
  274. <Popover trigger="manual" v-model:show={headData.cameraShow} placement="bottom" overlay={false}>
  275. {{
  276. reference: () => (
  277. <div
  278. id="tips-step-4"
  279. style={{ display: cameraBtn.value.display ? "" : "none" }}
  280. class={[styles.btn, cameraBtn.value.disabled && styles.disabled]}
  281. onClick={(e: Event) => {
  282. e.stopPropagation();
  283. headData.cameraShow = !headData.cameraShow;
  284. }}
  285. >
  286. <img style={{display: state.setting.camera ? 'none' : ''}} class={styles.iconBtn} src={iconCamera} />
  287. <img style={{display: state.setting.camera ? '' : 'none'}} class={styles.iconBtn} src={iconCameraOn} />
  288. <span>摄像头</span>
  289. </div>
  290. ),
  291. default: () => <Camera />,
  292. }}
  293. </Popover>
  294. {/* 速度按钮 */}
  295. <Popover trigger="manual" v-model:show={headData.speedShow} placement="bottom" overlay={false}>
  296. {{
  297. reference: () => (
  298. <div
  299. id="tips-step-4"
  300. style={{ display: speedBtn.value.display ? "" : "none" }}
  301. class={[styles.btn, speedBtn.value.disabled && styles.disabled]}
  302. onClick={(e: Event) => {
  303. e.stopPropagation();
  304. headData.speedShow = !headData.speedShow;
  305. }}
  306. >
  307. <Badge class={styles.badge} content={state.speed}>
  308. <img class={styles.iconBtn} src={icons.speed} />
  309. </Badge>
  310. <span>速度</span>
  311. </div>
  312. ),
  313. default: () => <Speed />,
  314. }}
  315. </Popover>
  316. {/* 转简谱按钮 */}
  317. <Popover trigger="manual" v-model:show={headData.musicTypeShow} placement="bottom-end" overlay={false}>
  318. {{
  319. reference: () => (
  320. <div
  321. style={{ display: converBtn.value.display ? "" : "none" }}
  322. class={[styles.btn, converBtn.value.disabled && styles.disabled]}
  323. onClick={(e: Event) => {
  324. e.stopPropagation();
  325. headData.musicTypeShow = !headData.musicTypeShow;
  326. }}
  327. >
  328. <img class={styles.iconBtn} src={icons["icon-zhuanpu"]} />
  329. <span>{state.musicRenderType === "staff" ? "转简谱" : "转五线谱"}</span>
  330. </div>
  331. ),
  332. default: () => <MusicType />,
  333. }}
  334. </Popover>
  335. {/* 设置按钮 */}
  336. <div
  337. style={{ display: settingBtn.value.display ? "" : "none" }}
  338. class={[styles.btn, settingBtn.value.disabled && styles.disabled]}
  339. onClick={() => (headData.settingMode = true)}
  340. >
  341. <img class={styles.iconBtn} src={icons.setting} />
  342. <span>设置</span>
  343. </div>
  344. {/* 播放按钮 */}
  345. <div
  346. style={{ display: playBtn.value.display ? "" : "none" }}
  347. class={[styles.btn, styles.playBtn, playBtn.value.disabled && styles.disabled]}
  348. id="tips-step-5"
  349. onClick={() => togglePlay()}
  350. >
  351. <div class={styles.btnWrap}>
  352. <img class={styles.iconBtn} src={state.playState === "paused" ? icons.play : icons.pause} />
  353. <Circle
  354. style={{ display: state.playState === "paused" ? "none" : "" }}
  355. class={styles.progress}
  356. stroke-width={80}
  357. currentRate={state.playProgress}
  358. rate={100}
  359. color="#FFC830"
  360. />
  361. </div>
  362. </div>
  363. {/* 重播按钮 */}
  364. <div
  365. style={{ display: resetBtn.value.display ? "" : "none" }}
  366. class={[styles.btn, styles.resetBtn, resetBtn.value.disabled && styles.disabled]}
  367. id="tips-step-7"
  368. onClick={() => handleResetPlay()}
  369. >
  370. <img class={styles.iconBtn} src={icons["reset"]} />
  371. </div>
  372. </div>
  373. <Popup v-model:show={headData.settingMode} class="popup-custom van-scale" transition="van-scale" teleport="body">
  374. <Settting onClose={() => (headData.settingMode = false)} />
  375. </Popup>
  376. <Popup v-model:show={headData.modeMode} teleport="body" class="popup-custom" position="bottom" closeOnClickOverlay={false} overlay={false}>
  377. <ModeTypeMode onClose={(value) => handleChangeModeType(value)} />
  378. </Popup>
  379. </div>
  380. );
  381. },
  382. });