index.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. import {
  2. defineComponent,
  3. onMounted,
  4. onUnmounted,
  5. reactive,
  6. ref,
  7. watch
  8. } from 'vue';
  9. import styles from './index.module.less';
  10. import { postMessage } from '@/helpers/native-message';
  11. import icon_title from './images/icon-title.png';
  12. import icon_back from './images/icon-back.png';
  13. import icon_setting from './images/icon-setting.png';
  14. import iconPlay from './images/icon-play.png';
  15. import iconPause from './images/icon-pause.png';
  16. import beat from './images/btn-2.png';
  17. import tempo from './images/btn-3.png';
  18. import randDom from './images/btn-1.png';
  19. import iconPlus from './images/icon-plus.png';
  20. import iconAdd from './images/icon-add.png';
  21. import { getImage } from './images/music';
  22. import '@vant/touch-emulator';
  23. // import j2 from './images/music/j-2.png';
  24. import { Popover, Popup, showToast } from 'vant';
  25. import SettingModal from './setting-modal';
  26. import {
  27. randomScoreElement,
  28. renderScore,
  29. setting,
  30. elementDirection
  31. } from './setting';
  32. import { handleStartTick, hendleEndTick } from './tick';
  33. import { handleStartBeat, hendleEndBeat } from './beat-tick';
  34. import { browser } from '@/helpers/utils';
  35. import { useRoute } from 'vue-router';
  36. export default defineComponent({
  37. name: 'tempo-practice',
  38. props: {
  39. dataJson: {
  40. type: Object,
  41. default: () => {}
  42. },
  43. modeType: {
  44. type: String,
  45. default: ''
  46. },
  47. show: {
  48. type: Boolean,
  49. default: false
  50. }
  51. },
  52. setup(props, { expose }) {
  53. const route = useRoute();
  54. const state = reactive({
  55. modeType: '' as any,
  56. platform: route.query.platform, // microapp 老师端应用里面打开单独处理返回逻辑
  57. win: route.query.win,
  58. settingStatus: false,
  59. speedList: [
  60. { text: '40', value: 40, color: '#060606' },
  61. { text: '50', value: 50, color: '#060606' },
  62. { text: '60', value: 60, color: '#060606' },
  63. { text: '70', value: 70, color: '#060606' },
  64. { text: '80', value: 80, color: '#060606' },
  65. { text: '90', value: 90, color: '#060606' },
  66. { text: '100', value: 100, color: '#060606' },
  67. { text: '110', value: 110, color: '#060606' },
  68. { text: '120', value: 120, color: '#060606' },
  69. { text: '130', value: 130, color: '#060606' },
  70. { text: '140', value: 140, color: '#060606' },
  71. { text: '150', value: 150, color: '#060606' },
  72. { text: '160', value: 160, color: '#060606' },
  73. { text: '170', value: 170, color: '#060606' },
  74. { text: '180', value: 180, color: '#060606' },
  75. { text: '190', value: 190, color: '#060606' },
  76. { text: '200', value: 200, color: '#060606' }
  77. ],
  78. dataJson: {} as any
  79. });
  80. // 返回
  81. const goback = () => {
  82. if (state.platform === 'microapp') {
  83. window.parent.postMessage(
  84. {
  85. api: 'iframe_exit'
  86. },
  87. '*'
  88. );
  89. return;
  90. }
  91. if (!browser().isApp) {
  92. window.close();
  93. return;
  94. }
  95. postMessage({ api: 'goBack' });
  96. };
  97. /** 播放切换 */
  98. const handlePlay = async () => {
  99. if (setting.playState === 'pause') {
  100. setting.playState = 'play';
  101. if (setting.playType === 'beat') {
  102. await handleStartTick();
  103. } else {
  104. await handleStartBeat();
  105. }
  106. } else {
  107. handleStop();
  108. }
  109. };
  110. /** 播放类型 */
  111. const handlePlayType = () => {
  112. handleStop();
  113. if (setting.playType === 'beat') {
  114. setting.playType = 'tempo';
  115. } else {
  116. setting.playType = 'beat';
  117. }
  118. };
  119. const handleStop = () => {
  120. setting.playState = 'pause';
  121. if (setting.playType === 'beat') {
  122. hendleEndTick();
  123. } else {
  124. hendleEndBeat();
  125. }
  126. };
  127. // {"element":"jianpu","beat":"4-4","barLine":"1","tempo":["1","2","3"]}'
  128. const onIframeHandle = (ev: MessageEvent) => {
  129. // 获取配置
  130. if (ev.data.api === 'getTempoSetting') {
  131. window.parent.postMessage(
  132. {
  133. api: 'getTempoSetting',
  134. data: JSON.stringify({
  135. setting: {
  136. element: setting.element,
  137. beat: setting.beat,
  138. barLine: setting.barLine,
  139. tempo: setting.tempo,
  140. scorePart: setting.scorePart,
  141. playType: setting.playType,
  142. speed: setting.speed
  143. },
  144. coverImg: ''
  145. })
  146. },
  147. '*'
  148. );
  149. }
  150. if (ev.data.api === 'setPlayState') {
  151. if (ev.data.data) {
  152. handlePlay();
  153. } else {
  154. handleStop();
  155. }
  156. }
  157. if (ev.data.api === 'resetPlay') {
  158. resetSetting();
  159. }
  160. };
  161. const resetSetting = () => {
  162. try {
  163. let dataJson = props.dataJson;
  164. if (route.query.dataJson) {
  165. dataJson = JSON.parse(route.query.dataJson as any);
  166. }
  167. console.log(dataJson, 'dataJson', props.dataJson);
  168. setting.element = dataJson.element;
  169. setting.beat = dataJson.beat;
  170. setting.barLine = dataJson.barLine;
  171. setting.tempo = dataJson.tempo;
  172. setting.scorePart = dataJson.scorePart;
  173. setting.playType = dataJson.playType;
  174. setting.speed = dataJson.speed;
  175. state.dataJson = dataJson;
  176. } catch {
  177. //
  178. }
  179. };
  180. // watch(
  181. // () => props.show,
  182. // val => {
  183. // console.log(val, props.show);
  184. // if (!val) {
  185. // // resetSetting();
  186. // handleStop();
  187. // } else {
  188. // resetSetting();
  189. // }
  190. // }
  191. // );
  192. onMounted(() => {
  193. if (route.query.modeType) {
  194. state.modeType = route.query.modeType;
  195. }
  196. resetSetting();
  197. state.speedList.forEach((item: any) => {
  198. if (item.value === setting.speed) item.color = '#1CACF1';
  199. });
  200. if (setting?.scorePart?.length <= 0) {
  201. renderScore();
  202. }
  203. window.addEventListener('message', onIframeHandle);
  204. });
  205. onUnmounted(() => {
  206. window.removeEventListener('message', onIframeHandle);
  207. });
  208. expose({
  209. resetSetting
  210. });
  211. return () => (
  212. <div
  213. onClick={() => {
  214. window.parent.postMessage(
  215. {
  216. api: 'clickTempo'
  217. },
  218. '*'
  219. );
  220. }}
  221. class={[
  222. styles.tempoPractice,
  223. state.win === 'pc' ? styles.pc : '',
  224. state.platform === 'modal' ? styles.modal : '',
  225. state.modeType === 'courseware' ? styles.courseware : ''
  226. ]}>
  227. <div class={styles.head}>
  228. {state.modeType !== 'courseware' && (
  229. <div
  230. class={[styles.back, styles.iconBack]}
  231. onClick={goback}
  232. style={{ cursor: 'pointer' }}>
  233. <img src={icon_back} />
  234. </div>
  235. )}
  236. <div class={styles.title}>
  237. <img src={icon_title} />
  238. </div>
  239. {state.modeType !== 'courseware' && (
  240. <div
  241. class={styles.back}
  242. style={{ cursor: 'pointer' }}
  243. onClick={() => {
  244. handleStop();
  245. state.settingStatus = true;
  246. }}>
  247. <img src={icon_setting} />
  248. </div>
  249. )}
  250. </div>
  251. <div class={styles.conCon}>
  252. <div class={styles.container}>
  253. {setting.scorePart?.map((item: any, i: number) => (
  254. <div
  255. class={[
  256. styles.beatSection,
  257. setting.scorePart.length >= 2 &&
  258. item.length !== 1 &&
  259. styles.small
  260. ]}>
  261. {item.map((child: any, jIndex: number) => (
  262. <div
  263. class={[styles.beat, child.selected ? styles.active : '']}
  264. onClick={(e: any) => {
  265. e.stopPropagation();
  266. }}>
  267. <div class={styles.direction}>
  268. <div
  269. class={styles.up}
  270. style={{ cursor: 'pointer' }}
  271. onClick={() => {
  272. if (setting.playState === 'play') return;
  273. if (setting.tempo.length <= 1) {
  274. showToast('无法切换,请选择至少2种节奏型');
  275. return;
  276. }
  277. // const obj = randomScoreElement(child.index);
  278. const obj = elementDirection('up', child.index);
  279. child.index = obj.index;
  280. child.url = obj.url;
  281. }}></div>
  282. <div
  283. class={styles.down}
  284. style={{ cursor: 'pointer' }}
  285. onClick={() => {
  286. if (setting.playState === 'play') return;
  287. if (setting.tempo.length <= 1) {
  288. showToast('无法切换,请选择至少2种节奏型');
  289. return;
  290. }
  291. // const obj = randomScoreElement(child.index);
  292. const obj = elementDirection('down', child.index);
  293. child.index = obj.index;
  294. child.url = obj.url;
  295. }}></div>
  296. </div>
  297. <div class={styles.imgSection}>
  298. <img src={getImage(child.url)} />
  299. </div>
  300. </div>
  301. ))}
  302. </div>
  303. ))}
  304. </div>
  305. </div>
  306. <div
  307. class={styles.footer}
  308. onClick={(e: any) => {
  309. e.stopPropagation();
  310. }}>
  311. {/* 播放 */}
  312. <div class={styles.play} onClick={handlePlay}>
  313. {setting.playState === 'pause' ? (
  314. <img src={iconPause} />
  315. ) : (
  316. <img src={iconPlay} />
  317. )}
  318. </div>
  319. {/* 播放类型 */}
  320. <div class={styles.playType} onClick={handlePlayType}>
  321. {setting.playType === 'beat' ? (
  322. <img src={beat} />
  323. ) : (
  324. <img src={tempo} />
  325. )}
  326. </div>
  327. {/* 随机生成 */}
  328. <div
  329. class={styles.randomTempo}
  330. onClick={() => {
  331. renderScore();
  332. handleStop();
  333. }}>
  334. <img src={randDom} />
  335. </div>
  336. {/* 速度 */}
  337. <div class={styles.speedChange}>
  338. <img
  339. src={iconPlus}
  340. class={styles.speedPlus}
  341. onClick={() => {
  342. if (setting.speed <= 40) return;
  343. setting.speed -= 1;
  344. handleStop();
  345. state.speedList.forEach((item: any) => {
  346. if (item.value === setting.speed) {
  347. item.color = '#1CACF1';
  348. setting.speed = setting.speed;
  349. } else {
  350. item.color = '#060606';
  351. }
  352. });
  353. }}
  354. />
  355. <Popover
  356. placement="top"
  357. class={styles.popupContainer}
  358. actions={state.speedList}
  359. onSelect={(val: any) => {
  360. if (val.value === setting.speed) return;
  361. state.speedList.forEach((item: any) => {
  362. if (item.value === val.value) {
  363. item.color = '#1CACF1';
  364. setting.speed = val.value;
  365. } else {
  366. item.color = '#060606';
  367. }
  368. });
  369. handleStop();
  370. }}>
  371. {{
  372. reference: () => (
  373. <div class={styles.speedNum}>{setting.speed}</div>
  374. )
  375. }}
  376. </Popover>
  377. <img
  378. src={iconAdd}
  379. class={styles.speedAdd}
  380. onClick={() => {
  381. if (setting.speed >= 200) return;
  382. setting.speed += 1;
  383. handleStop();
  384. state.speedList.forEach((item: any) => {
  385. if (item.value === setting.speed) {
  386. item.color = '#1CACF1';
  387. setting.speed = setting.speed;
  388. } else {
  389. item.color = '#060606';
  390. }
  391. });
  392. }}
  393. />
  394. </div>
  395. </div>
  396. <Popup v-model:show={state.settingStatus} class={styles.settingPopup}>
  397. <SettingModal
  398. dataJson={state.dataJson}
  399. onClose={() => (state.settingStatus = false)}
  400. />
  401. </Popup>
  402. </div>
  403. );
  404. }
  405. });