index.tsx 15 KB

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