index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. class={[
  214. styles.tempoPractice,
  215. state.win === 'pc' ? styles.pc : '',
  216. state.platform === 'modal' ? styles.modal : '',
  217. state.modeType === 'courseware' ? styles.courseware : ''
  218. ]}>
  219. <div class={styles.head}>
  220. {state.modeType !== 'courseware' && (
  221. <div
  222. class={[styles.back, styles.iconBack]}
  223. onClick={goback}
  224. style={{ cursor: 'pointer' }}>
  225. <img src={icon_back} />
  226. </div>
  227. )}
  228. <div class={styles.title}>
  229. <img src={icon_title} />
  230. </div>
  231. {state.modeType !== 'courseware' && (
  232. <div
  233. class={styles.back}
  234. style={{ cursor: 'pointer' }}
  235. onClick={() => {
  236. handleStop();
  237. state.settingStatus = true;
  238. }}>
  239. <img src={icon_setting} />
  240. </div>
  241. )}
  242. </div>
  243. <div class={styles.conCon}>
  244. <div class={styles.container}>
  245. {setting.scorePart?.map((item: any, i: number) => (
  246. <div
  247. class={[
  248. styles.beatSection,
  249. setting.scorePart.length >= 2 &&
  250. item.length !== 1 &&
  251. styles.small
  252. ]}>
  253. {item.map((child: any, jIndex: number) => (
  254. <div
  255. class={[styles.beat, child.selected ? styles.active : '']}>
  256. <div class={styles.direction}>
  257. <div
  258. class={styles.up}
  259. style={{ cursor: 'pointer' }}
  260. onClick={() => {
  261. if (setting.playState === 'play') return;
  262. if (setting.tempo.length <= 1) {
  263. showToast('无法切换,请选择至少2种节奏型');
  264. return;
  265. }
  266. // const obj = randomScoreElement(child.index);
  267. const obj = elementDirection('up', child.index);
  268. child.index = obj.index;
  269. child.url = obj.url;
  270. }}></div>
  271. <div
  272. class={styles.down}
  273. style={{ cursor: 'pointer' }}
  274. onClick={() => {
  275. if (setting.playState === 'play') return;
  276. if (setting.tempo.length <= 1) {
  277. showToast('无法切换,请选择至少2种节奏型');
  278. return;
  279. }
  280. // const obj = randomScoreElement(child.index);
  281. const obj = elementDirection('down', child.index);
  282. child.index = obj.index;
  283. child.url = obj.url;
  284. }}></div>
  285. </div>
  286. <div class={styles.imgSection}>
  287. <img src={getImage(child.url)} />
  288. </div>
  289. </div>
  290. ))}
  291. </div>
  292. ))}
  293. </div>
  294. </div>
  295. <div class={styles.footer}>
  296. {/* 播放 */}
  297. <div class={styles.play} onClick={handlePlay}>
  298. {setting.playState === 'pause' ? (
  299. <img src={iconPause} />
  300. ) : (
  301. <img src={iconPlay} />
  302. )}
  303. </div>
  304. {/* 播放类型 */}
  305. <div class={styles.playType} onClick={handlePlayType}>
  306. {setting.playType === 'beat' ? (
  307. <img src={beat} />
  308. ) : (
  309. <img src={tempo} />
  310. )}
  311. </div>
  312. {/* 随机生成 */}
  313. <div
  314. class={styles.randomTempo}
  315. onClick={() => {
  316. renderScore();
  317. handleStop();
  318. }}>
  319. <img src={randDom} />
  320. </div>
  321. {/* 速度 */}
  322. <div class={styles.speedChange}>
  323. <img
  324. src={iconPlus}
  325. class={styles.speedPlus}
  326. onClick={() => {
  327. if (setting.speed <= 40) return;
  328. setting.speed -= 1;
  329. handleStop();
  330. state.speedList.forEach((item: any) => {
  331. if (item.value === setting.speed) {
  332. item.color = '#1CACF1';
  333. setting.speed = setting.speed;
  334. } else {
  335. item.color = '#060606';
  336. }
  337. });
  338. }}
  339. />
  340. <Popover
  341. placement="top"
  342. class={styles.popupContainer}
  343. actions={state.speedList}
  344. onSelect={(val: any) => {
  345. if (val.value === setting.speed) return;
  346. state.speedList.forEach((item: any) => {
  347. if (item.value === val.value) {
  348. item.color = '#1CACF1';
  349. setting.speed = val.value;
  350. } else {
  351. item.color = '#060606';
  352. }
  353. });
  354. handleStop();
  355. }}>
  356. {{
  357. reference: () => (
  358. <div class={styles.speedNum}>{setting.speed}</div>
  359. )
  360. }}
  361. </Popover>
  362. <img
  363. src={iconAdd}
  364. class={styles.speedAdd}
  365. onClick={() => {
  366. if (setting.speed >= 200) return;
  367. setting.speed += 1;
  368. handleStop();
  369. state.speedList.forEach((item: any) => {
  370. if (item.value === setting.speed) {
  371. item.color = '#1CACF1';
  372. setting.speed = setting.speed;
  373. } else {
  374. item.color = '#060606';
  375. }
  376. });
  377. }}
  378. />
  379. </div>
  380. </div>
  381. <Popup v-model:show={state.settingStatus} class={styles.settingPopup}>
  382. <SettingModal
  383. dataJson={state.dataJson}
  384. onClose={() => (state.settingStatus = false)}
  385. />
  386. </Popup>
  387. </div>
  388. );
  389. }
  390. });