index.tsx 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322
  1. import { PropType, computed, defineComponent, nextTick, onBeforeMount, onMounted, onUnmounted, reactive, ref } from "vue";
  2. import styles from "./index.module.less";
  3. import icons from "./image/icons.json";
  4. import { FIGNER_INSTRUMENT_DATA, FIGNER_INSTRUMENT_REALKEY, IFIGNER_INSTRUMENT_Note } from "/src/view/figner-preview";
  5. import { ITypeFingering, IVocals, getFingeringConfig, mappingVoicePart, subjectFingering } from "/src/view/fingering/fingering-config";
  6. import { Howl } from "howler";
  7. import { storeData } from "/src/store";
  8. import { api_back, api_cloudLoading, api_setRequestedOrientation, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
  9. import Hammer from "hammerjs";
  10. import { Button, Icon, Loading, Popover, Popup, Progress, Space, showToast } from "vant";
  11. import GuideIndex from "./guide/guide-index";
  12. import { getQuery } from "/src/utils/queryString";
  13. import { browser } from "/src/utils";
  14. import { usePageVisibility } from "@vant/use";
  15. import { watch } from "vue";
  16. import icon_loading_img from "./image/icon_loading_img.png";
  17. import state, { IPlatform } from "/src/state";
  18. import { api_subjectList, getSubjectList } from "../api";
  19. import ChangeSubject from "./change-subject";
  20. export default defineComponent({
  21. name: "viewFigner",
  22. emits: ["close"],
  23. props: {
  24. show: {
  25. type: Boolean,
  26. default: true,
  27. },
  28. isComponent: {
  29. type: Boolean,
  30. default: false,
  31. },
  32. subject: {
  33. type: String as PropType<IVocals>,
  34. default: "",
  35. },
  36. },
  37. setup(props, { emit }) {
  38. const query = getQuery();
  39. const browsInfo = browser();
  40. const code = mappingVoicePart(query.code, "INSTRUMENT");
  41. const subject = props.isComponent ? props.subject || "pan-flute" : code || "pan-flute";
  42. const data = reactive({
  43. loading: true,
  44. subject: subject as any,
  45. realKey: 0,
  46. notes: [] as IFIGNER_INSTRUMENT_Note[],
  47. tones: [] as IFIGNER_INSTRUMENT_Note[],
  48. activeTone: {} as IFIGNER_INSTRUMENT_Note,
  49. popupActiveTone: {} as IFIGNER_INSTRUMENT_Note,
  50. activeToneName: "",
  51. soundFonts: {} as any,
  52. viewIndex: 0,
  53. viewTotal: 1,
  54. noteAudio: null as unknown as Howl,
  55. transform: {
  56. scale: 1,
  57. x: 0,
  58. y: 0,
  59. startScale: 1,
  60. startX: 0,
  61. startY: 0,
  62. transition: "",
  63. },
  64. tipShow: false,
  65. tips: [] as IFIGNER_INSTRUMENT_Note[],
  66. tnoteShow: false,
  67. loadingSoundFonts: true,
  68. loadingSoundProgress: 0,
  69. changeSubjectShow: false,
  70. huaweiPad: navigator?.userAgent?.includes("UAWEIVRD-W09") ? true : false,
  71. paddingTop: "",
  72. paddingLeft: "",
  73. subjects: [] as any,
  74. fingeringModeList: [
  75. {
  76. text: "指法模式",
  77. value: "fingeringMode",
  78. icon: icons.icon_click,
  79. },
  80. {
  81. text: "听音模式",
  82. value: "listenMode",
  83. icon: icons.icon_listen,
  84. },
  85. {
  86. text: "音阶模式",
  87. value: "scaleMode",
  88. icon: icons.icon_mode,
  89. },
  90. ],
  91. fingeringMode: query.type || ("scaleMode" as "fingeringMode" | "listenMode" | "scaleMode"), // 模式
  92. noteType: "all" as "#c" | "all", // 音调
  93. loadingDom: false, // 切换乐器时需要重置
  94. loadingImg: false, // 切换模式,加载图片
  95. });
  96. const fingerData = reactive({
  97. relationshipIndex: 0,
  98. subject: null as unknown as ITypeFingering,
  99. fingeringInfo: subjectFingering(data.subject),
  100. });
  101. if (!props.isComponent) {
  102. state.fingeringInfo = fingerData.fingeringInfo;
  103. }
  104. const getAPPData = async (type: "top" | "left") => {
  105. const screenData = await isSpecialShapedScreen();
  106. if (screenData?.content) {
  107. // console.log("🚀 ~ screenData:", screenData.content);
  108. const { isSpecialShapedScreen, notchHeight } = screenData.content;
  109. if (isSpecialShapedScreen) {
  110. if (type === "top") {
  111. data.paddingTop = 25 + "px";
  112. }
  113. if (type === "left") {
  114. data.paddingLeft = 25 + "px";
  115. }
  116. }
  117. }
  118. };
  119. const getHeadTop = () => {
  120. if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 1) {
  121. getAPPData("top");
  122. }
  123. if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 0) {
  124. getAPPData("left");
  125. }
  126. };
  127. const getNotes = () => {
  128. const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
  129. if (fignerData) {
  130. data.tones = fignerData.tones || [];
  131. if (data.tones.length) {
  132. data.activeTone = data.tones[0];
  133. data.popupActiveTone = data.tones[0];
  134. }
  135. data.tips = fignerData.tips || [];
  136. setNotes();
  137. setTimeout(() => {
  138. data.loading = false;
  139. }, 600);
  140. }
  141. };
  142. const setNotes = () => {
  143. const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
  144. if (fignerData) {
  145. const tempNotes = fignerData[`list${data.activeTone.realName || ""}`];
  146. const appendNote: any = [];
  147. tempNotes.forEach((note: any) => {
  148. note.steps = new Array(Math.abs(note.step)).fill(1);
  149. if (FIGNER_INSTRUMENT_REALKEY.includes(note.realKey)) {
  150. appendNote.push(note);
  151. }
  152. });
  153. // 判断是音符状态
  154. data.notes = data.noteType === "#c" ? appendNote : tempNotes;
  155. // data.notes = fignerData[`list${data.activeTone.realName || ""}`];
  156. }
  157. };
  158. const getFingeringData = async () => {
  159. const subject: any = data.subject + (data.viewIndex === 0 ? "" : data.viewIndex);
  160. console.log("🚀 ~ subject:", subject);
  161. fingerData.subject = await getFingeringConfig(subject);
  162. };
  163. const createAudio = (url: string) => {
  164. return new Promise((resolve, reject) => {
  165. const noteAudio = new Howl({
  166. src: url,
  167. loop: true,
  168. onload: () => {
  169. resolve(noteAudio);
  170. },
  171. onloaderror: () => {
  172. reject(new Error(`加载音频失败`));
  173. },
  174. });
  175. });
  176. };
  177. const getSounFonts = async () => {
  178. const pathname = /(192|localhost)/.test(location.origin) ? "/" : location.pathname;
  179. data.loadingSoundFonts = true;
  180. try {
  181. data.loadingSoundProgress = 0;
  182. for (let i = 0; i < data.notes.length; i++) {
  183. const note = data.notes[i];
  184. // console.log("🚀 ~ note:", i);
  185. let url = `${pathname}soundfonts/${data.subject}/`;
  186. url += note.realName;
  187. url += ".mp3";
  188. data.soundFonts[note.realKey] = await createAudio(url);
  189. data.loadingSoundProgress = Math.floor(((i + 1) / data.notes.length) * 100);
  190. }
  191. data.loadingSoundProgress = 100;
  192. } catch (e: any) {
  193. //
  194. showToast(e.message);
  195. }
  196. api_cloudLoading();
  197. data.loadingSoundFonts = false;
  198. };
  199. // const selectSubjectType = (subject: string) => {
  200. // data.subjects.forEach((item: any) => {
  201. // if (item.value === subject) {
  202. // item.className = styles.selected;
  203. // } else {
  204. // item.className = "";
  205. // }
  206. // });
  207. // };
  208. // 切换当前模式
  209. const onChangeFingeringModel = () => {
  210. //
  211. if (playAction.listenLock) return;
  212. if (playAction.showAnswerLoading) return;
  213. data.loadingImg = true;
  214. if (data.fingeringMode === "scaleMode") {
  215. if (["pan-flute", "ocarina"].includes(data.subject)) {
  216. data.viewIndex = 1;
  217. } else {
  218. data.viewIndex = 0;
  219. }
  220. const o: any = {
  221. "pan-flute": 2,
  222. ocarina: 2,
  223. piccolo: 2,
  224. "hulusi-flute": 2,
  225. "baroque-recorder": 2,
  226. };
  227. data.viewTotal = o[data.subject] || 1;
  228. data.fingeringMode = "listenMode";
  229. } else if (data.fingeringMode === "listenMode") {
  230. data.fingeringMode = "fingeringMode";
  231. } else if (data.fingeringMode === "fingeringMode") {
  232. data.fingeringMode = "scaleMode";
  233. data.viewIndex = 0;
  234. data.noteType = "all";
  235. }
  236. data.tipShow = false;
  237. resetMode(true, 0);
  238. setTimeout(() => {
  239. __init(false);
  240. }, 100);
  241. };
  242. const __init = async (loadSong = true) => {
  243. data.loadingDom = true;
  244. getNotes();
  245. // selectSubjectType(data.subject);
  246. if (data.fingeringMode === "fingeringMode") {
  247. if (data.subject === "pan-flute") {
  248. data.viewIndex = 3;
  249. } else if (["pan-flute", "ocarina", "melodica"].includes(data.subject)) {
  250. data.viewIndex = 1;
  251. }
  252. } else {
  253. if (["pan-flute", "ocarina"].includes(data.subject)) {
  254. data.viewIndex = 1;
  255. }
  256. }
  257. const o: any = {
  258. "pan-flute": 2,
  259. ocarina: 2,
  260. piccolo: 2,
  261. "hulusi-flute": 2,
  262. "baroque-recorder": 2,
  263. };
  264. data.viewTotal = o[data.subject] || 1;
  265. getFingeringData();
  266. getHeadTop();
  267. if (loadSong) {
  268. await getSounFonts();
  269. }
  270. data.loadingDom = false;
  271. data.loadingImg = false;
  272. };
  273. // 获取声部
  274. const getSubjects = async () => {
  275. try {
  276. // api_subjectList
  277. const subjects = await api_subjectList({
  278. enableFlag: true,
  279. delFlag: 0,
  280. page: 1,
  281. rows: 999,
  282. });
  283. const rows = subjects.data || [];
  284. rows.forEach((row: any) => {
  285. const tempList: any = {
  286. text: row.name,
  287. value: mappingVoicePart(row.code, "INSTRUMENT"),
  288. id: row.id,
  289. children: [] as any,
  290. };
  291. if (row.instruments && row.instruments.length > 0) {
  292. if (row.instruments.length > 1) {
  293. row.instruments.forEach((i: any) => {
  294. tempList.children.push({
  295. text: i.name,
  296. id: i.id,
  297. value: mappingVoicePart(i.code, "INSTRUMENT"),
  298. });
  299. });
  300. } else {
  301. const singleRow = row.instruments[0];
  302. if (singleRow.code) {
  303. tempList.value = mappingVoicePart(singleRow.code, "INSTRUMENT");
  304. tempList.id = singleRow.id;
  305. }
  306. }
  307. }
  308. data.subjects.push(tempList);
  309. });
  310. data.subjects.forEach((item: any) => {
  311. if (item.value === data.subject && item.children?.length > 1) {
  312. data.subject = item.children[0].value;
  313. }
  314. });
  315. } catch (e) {
  316. //
  317. }
  318. };
  319. onBeforeMount(async () => {
  320. if (browser().isApp) {
  321. state.platform = "APP" as IPlatform;
  322. } else {
  323. state.platform = query.platform?.toLocaleUpperCase() || "";
  324. }
  325. if (state.platform === IPlatform.PC) {
  326. document.title = "听音练习";
  327. }
  328. await getSubjects();
  329. __init();
  330. });
  331. /**
  332. * 播放音频
  333. * @param item 音频节点
  334. * @param showNote 是否显示对应的指法
  335. * @returns
  336. */
  337. const noteClick = (item: IFIGNER_INSTRUMENT_Note, showNote = true) => {
  338. // console.log('音高', item.realKey)
  339. if (data.noteAudio) {
  340. data.noteAudio.stop();
  341. if (data.realKey === item.realKey) {
  342. data.realKey = 0;
  343. data.noteAudio = null as unknown as Howl;
  344. return;
  345. }
  346. }
  347. if (showNote) {
  348. data.realKey = item.realKey;
  349. }
  350. data.noteAudio = data.soundFonts[item.realKey];
  351. if (data.noteAudio) {
  352. data.noteAudio.play();
  353. }
  354. };
  355. const handleStop = () => {
  356. if (data.noteAudio) {
  357. data.noteAudio.stop();
  358. data.realKey = 0;
  359. data.noteAudio = null as unknown as Howl;
  360. }
  361. };
  362. /** 返回 */
  363. const handleBack = () => {
  364. // platform: query.platform,
  365. handleStop();
  366. if (props.isComponent) {
  367. // 返回的时候默认横屏
  368. api_setRequestedOrientation(0);
  369. emit("close");
  370. return;
  371. } else if (state.platform === IPlatform.PC) {
  372. console.log(1, query);
  373. if (query.matchMedia == 1) {
  374. // 老师端,首页
  375. window.parent.postMessage(
  376. {
  377. api: "iframe_exit",
  378. },
  379. "*"
  380. );
  381. return;
  382. } else {
  383. window.close();
  384. return;
  385. }
  386. // if (fingerData.fingeringInfo.orientation === 0) {
  387. // api_setRequestedOrientation(1);
  388. // }
  389. }
  390. // 不在APP中,
  391. if (!storeData.isApp) {
  392. window.close();
  393. return;
  394. }
  395. api_back();
  396. };
  397. // 排箫,默认0.9显示
  398. const setDefaultScale = () => {
  399. if (data.subject === "pan-flute") {
  400. data.transform.scale = 0.9;
  401. data.transform.startScale = 0.9;
  402. }
  403. };
  404. onMounted(() => {
  405. loadElement();
  406. api_setStatusBarVisibility();
  407. });
  408. const loadElement = () => {
  409. const fingeringContainer = document.getElementById("fingeringContainer");
  410. setDefaultScale();
  411. // console.log("🚀 ~ fingeringContainer:", fingeringContainer);
  412. const mc = new Hammer.Manager(fingeringContainer as HTMLElement);
  413. mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
  414. mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get("pan")]);
  415. // mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
  416. // mc.get("pinch").set({ enable: true });
  417. mc.on("panstart pinchstart", function (ev) {
  418. data.transform.transition = "";
  419. });
  420. mc.on("panmove pinchmove", function (ev) {
  421. if (ev.type === "pinchmove") {
  422. // console.log("🚀 ~ ev:", ev.type, ev.scale, ev.deltaX, ev.deltaY);
  423. data.transform.scale = ev.scale * data.transform.startScale;
  424. data.transform.x = data.transform.startX + ev.deltaX;
  425. data.transform.y = data.transform.startY + ev.deltaY;
  426. }
  427. if (ev.type === "panmove") {
  428. // console.log("🚀 ~ ev:", ev.type, ev.deltaX, ev.deltaY);
  429. data.transform.x = data.transform.startX + ev.deltaX;
  430. data.transform.y = data.transform.startY + ev.deltaY;
  431. }
  432. });
  433. //
  434. mc.on("hammer.input", function (ev) {
  435. if (ev.isFinal) {
  436. data.transform.startScale = data.transform.scale;
  437. data.transform.startX = data.transform.x;
  438. data.transform.startY = data.transform.y;
  439. }
  440. });
  441. };
  442. const resetElement = () => {
  443. data.transform.transition = "all 0.3s";
  444. nextTick(() => {
  445. data.transform.scale = data.subject === "pan-flute" ? 0.9 : 1;
  446. data.transform.x = 0;
  447. data.transform.y = 0;
  448. data.transform.startScale = data.subject === "pan-flute" ? 0.9 : 1;
  449. data.transform.startX = 0;
  450. data.transform.startY = 0;
  451. });
  452. };
  453. // 判断乐器是否移动
  454. const instrumentTranstion = computed(() => {
  455. const transform = data.transform;
  456. if (transform.scale !== 1 || transform.x !== 0 || transform.y !== 0 || transform.startScale !== 1 || transform.startX !== 0 || transform.startY !== 0) {
  457. return true;
  458. } else {
  459. return false;
  460. }
  461. });
  462. const pageVisible = usePageVisibility();
  463. watch(
  464. () => pageVisible.value,
  465. (val) => {
  466. if (val === "hidden") {
  467. console.log("页面隐藏停止播放");
  468. handleStop();
  469. gaumntPause();
  470. }
  471. }
  472. );
  473. /** 课件播放 */
  474. const changePlay = (res: any) => {
  475. if (res?.data?.api === "setPlayState") {
  476. handleStop();
  477. gaumntPause();
  478. }
  479. };
  480. const noteBoxRef = ref();
  481. const scrollNoteBox = (type: "left" | "right") => {
  482. const width = noteBoxRef.value.offsetWidth / 2;
  483. (noteBoxRef.value as unknown as HTMLElement).scrollBy({
  484. left: type === "left" ? -width : width,
  485. behavior: "smooth",
  486. });
  487. };
  488. const playStatus = reactive({
  489. gamut: false, // 是否播放音阶
  490. gamutTimer: null as any, // 播放音阶定时器
  491. answer: false, // 是否显示答案
  492. action: false, // 是否开始播放
  493. });
  494. /** 音符切换 */
  495. const noteChangeShow = () => {
  496. if (playStatus.action) {
  497. if (playAction.listenLock) return;
  498. playAction.resetAction = true;
  499. resetMode(true, 0);
  500. }
  501. // // 播放音阶时不能切换
  502. // if (playStatus.gamut) return;
  503. // // 开始答题不能切换
  504. // if (playStatus.action) return;
  505. playStatus.gamut = false;
  506. gaumntPause();
  507. if (data.noteType === "all") {
  508. data.noteType = "#c";
  509. } else {
  510. data.noteType = "all";
  511. }
  512. getNotes();
  513. setTimeout(() => {
  514. playAction.resetAction = false;
  515. }, 2000);
  516. };
  517. // 开始播放音阶
  518. const onGamutPlayOrPause = async () => {
  519. playAction.resetAction = false;
  520. if (playStatus.gamut) {
  521. playStatus.gamut = false;
  522. gaumntPause();
  523. } else {
  524. // 不管当前显示在哪个音老师滚动到开始位置
  525. (noteBoxRef.value as unknown as HTMLElement).scroll({
  526. left: 0,
  527. top: 0,
  528. behavior: "smooth",
  529. });
  530. playStatus.gamut = true;
  531. const notes = data.notes;
  532. let scrollCount = 0;
  533. for (let i = 0; i < notes.length; i++) {
  534. if (!playStatus.gamut) return false;
  535. const activeDom = document.querySelectorAll(".note-class")[i] as any;
  536. if (activeDom.offsetLeft >= noteBoxRef.value.offsetWidth + (noteBoxRef.value.offsetWidth / 2) * scrollCount - activeDom.offsetWidth) {
  537. scrollNoteBox("right");
  538. scrollCount++;
  539. }
  540. await gaumtPlay(notes[i]);
  541. }
  542. // // 处理播放到最后一个
  543. setTimeout(() => {
  544. playStatus.gamut = false;
  545. gaumntPause();
  546. }, 667);
  547. }
  548. };
  549. const gaumtPlay = (note: any, status?: boolean) => {
  550. return new Promise((resolve) => {
  551. playStatus.gamutTimer = setTimeout(() => {
  552. if (playStatus.gamut || status) {
  553. noteClick(note);
  554. }
  555. resolve(note);
  556. }, 667);
  557. });
  558. };
  559. const gaumntPause = () => {
  560. clearTimeout(playStatus.gamutTimer);
  561. if (data.noteAudio) {
  562. data.noteAudio.stop();
  563. data.realKey = 0;
  564. data.noteAudio = null as unknown as Howl;
  565. }
  566. };
  567. /** 开始播放 */
  568. const playAction = reactive({
  569. exampleAnser: {} as any, // 示例声音
  570. standardAnswer: {} as any, // 标准答案key
  571. showAnswerLoading: false, // 显示按答案中
  572. listenModeStatus: false, // 是否开始了模式
  573. listenLock: false,
  574. listenTipsStatus: false, // 开始播放状态
  575. resetAction: false, // 是否重置
  576. /** 0: 未答,1: 答对,2: 答错 */
  577. userAnswerStatus: 0 as 0 | 1 | 2, // 用户回答状态
  578. userAnswer: {} as any, // 用户答的数据
  579. });
  580. const onActionPlay = async () => {
  581. playAction.resetAction = false;
  582. if (playAction.listenLock) return;
  583. if (playAction.showAnswerLoading) return;
  584. playStatus.action = true;
  585. playStatus.answer = true;
  586. // 先暂停播放声音
  587. gaumntPause();
  588. if (data.fingeringMode === "fingeringMode") {
  589. onFingeringMode();
  590. } else if (data.fingeringMode === "listenMode") {
  591. if (playAction.listenModeStatus) {
  592. playAction.listenLock = true;
  593. await fingeringPlay(playAction.standardAnswer, 1500, false);
  594. gaumntPause();
  595. playAction.listenLock = false;
  596. } else {
  597. onListenMode();
  598. }
  599. }
  600. };
  601. // 指法模式
  602. const fingeringPlay = (note: any, timer = 1500, showNote = true) => {
  603. return new Promise((resolve) => {
  604. noteClick(note, showNote);
  605. setTimeout(() => {
  606. resolve(note);
  607. }, timer);
  608. });
  609. };
  610. const onFingeringMode = () => {
  611. const randomIndex = Math.floor(Math.random() * data.notes.length);
  612. playAction.standardAnswer = data.notes[randomIndex];
  613. data.realKey = data.notes[randomIndex].realKey;
  614. if (playAction.listenModeStatus) {
  615. return;
  616. }
  617. playAction.listenModeStatus = true; // 是否开始听音
  618. playAction.listenLock = true; // 锁
  619. playAction.listenTipsStatus = true;
  620. setTimeout(() => {
  621. playAction.listenTipsStatus = false;
  622. playAction.listenLock = false; // 锁
  623. }, 2000);
  624. };
  625. // 听音模式
  626. const onListenMode = async () => {
  627. playAction.listenModeStatus = true; // 是否开始听音
  628. playAction.listenLock = true; // 锁
  629. playAction.listenTipsStatus = true;
  630. // 设置并保存示例数据
  631. let randomIndex = data.notes.findIndex((item: any) => item.realKey === 67); // Math.floor(Math.random() * data.notes.length);
  632. playAction.exampleAnser = data.notes[randomIndex];
  633. data.realKey = playAction.exampleAnser.realKey;
  634. scrollAnswer(playAction.exampleAnser.realKey);
  635. await fingeringPlay(playAction.exampleAnser);
  636. data.realKey = 0;
  637. playAction.exampleAnser = {};
  638. gaumntPause();
  639. setTimeout(async () => {
  640. // 设置答题数据
  641. randomIndex = Math.floor(Math.random() * data.notes.length);
  642. playAction.standardAnswer = data.notes[randomIndex];
  643. await fingeringPlay(data.notes[randomIndex], 1500, false);
  644. gaumntPause();
  645. playAction.listenLock = false;
  646. playAction.listenTipsStatus = false;
  647. }, 1000);
  648. };
  649. // 显示答案
  650. const onShowAnswer = async () => {
  651. if (playAction.listenLock) return;
  652. playAction.showAnswerLoading = true;
  653. scrollAnswer(playAction.standardAnswer.realKey);
  654. await fingeringPlay(playAction.standardAnswer);
  655. resetMode(true, 0);
  656. // }
  657. };
  658. // 滚动到对应答案位置
  659. const scrollAnswer = (realKey?: any) => {
  660. const tempRealKey = realKey || data.realKey;
  661. const index = data.notes.findIndex((item: any) => item.realKey === tempRealKey);
  662. const activeDom = document.querySelectorAll(".note-class")[index] as any;
  663. if (activeDom) {
  664. const aWidth = activeDom.offsetWidth;
  665. const width = noteBoxRef.value.offsetWidth;
  666. const aLeft = Math.max(activeDom?.offsetLeft - aWidth, 0);
  667. (noteBoxRef.value as unknown as HTMLElement).scroll({
  668. left: Math.max(aLeft - width / 2, 0),
  669. top: 0,
  670. behavior: "smooth",
  671. });
  672. }
  673. };
  674. /**
  675. * 重置播放状态
  676. * @param status 是否全部重置
  677. * @param timer 延时时长(默认2s)
  678. */
  679. const resetMode = (status = true, timer = 2000) => {
  680. // 2秒钟后重置
  681. setTimeout(() => {
  682. gaumntPause();
  683. if (status) {
  684. playAction.standardAnswer = {};
  685. playAction.showAnswerLoading = false;
  686. playAction.userAnswerStatus = 0;
  687. playAction.userAnswer = {};
  688. playAction.listenModeStatus = false;
  689. playStatus.action = false;
  690. playStatus.answer = false;
  691. playStatus.gamut = false;
  692. data.realKey = 0;
  693. } else {
  694. playAction.userAnswerStatus = 0;
  695. playAction.userAnswer = {};
  696. }
  697. }, timer);
  698. };
  699. /** 滚轮缩放 */
  700. const handleWheel = (e: WheelEvent) => {
  701. e.preventDefault();
  702. if (e.deltaY > 0) {
  703. data.transform.scale -= 0.1;
  704. if (data.transform.scale <= 0.5) {
  705. data.transform.scale = 0.5;
  706. }
  707. } else {
  708. data.transform.scale += 0.1;
  709. if (data.transform.scale >= 2) {
  710. data.transform.scale = 2;
  711. }
  712. }
  713. };
  714. onMounted(() => {
  715. window.addEventListener("message", changePlay);
  716. const fingeringContainer = document.getElementById("fingeringContainer");
  717. fingeringContainer?.addEventListener("wheel", handleWheel);
  718. });
  719. onUnmounted(() => {
  720. window.removeEventListener("message", changePlay);
  721. const fingeringContainer = document.getElementById("fingeringContainer");
  722. fingeringContainer?.removeEventListener("wheel", handleWheel);
  723. document.title = "Ai学练";
  724. });
  725. const containerBox = computed(() => {
  726. if (state.platform === IPlatform.PC || query.modelType) {
  727. return {
  728. paddingTop: "1rem",
  729. paddingBottom: "",
  730. };
  731. }
  732. if (data.fingeringMode === "scaleMode") {
  733. if (data.subject === "hulusi-flute") {
  734. return {
  735. paddingTop: "3.1rem",
  736. paddingBottom: ".8rem",
  737. };
  738. } else if (data.subject === "piccolo" || data.subject === "baroque-recorder") {
  739. return {
  740. paddingTop: "4rem",
  741. paddingBottom: ".8rem",
  742. };
  743. } else if (data.subject === "pan-flute") {
  744. return {
  745. paddingTop: "0",
  746. paddingBottom: "0",
  747. };
  748. } else if (data.subject === "ocarina") {
  749. return {
  750. paddingTop: "1.2rem",
  751. paddingBottom: "0",
  752. };
  753. } else if (data.subject === "melodica") {
  754. return {
  755. paddingTop: "2.8rem",
  756. paddingBottom: "1.8rem",
  757. };
  758. } else {
  759. return {
  760. paddingTop: "",
  761. paddingBottom: "",
  762. };
  763. }
  764. } else {
  765. if (data.subject === "hulusi-flute") {
  766. return {
  767. paddingTop: "3.1rem",
  768. paddingBottom: "0rem",
  769. };
  770. } else if (data.subject === "piccolo" || data.subject === "baroque-recorder") {
  771. return {
  772. paddingTop: "3rem",
  773. paddingBottom: ".5rem",
  774. };
  775. } else if (data.subject === "pan-flute") {
  776. return {
  777. paddingTop: "0",
  778. paddingBottom: "0",
  779. };
  780. } else if (data.subject === "ocarina") {
  781. return {
  782. paddingTop: "1rem",
  783. paddingBottom: "0",
  784. };
  785. } else if (data.subject === "melodica") {
  786. return {
  787. paddingTop: "2.8rem",
  788. paddingBottom: "0.8rem",
  789. };
  790. } else {
  791. return {
  792. paddingTop: "",
  793. paddingBottom: "",
  794. };
  795. }
  796. }
  797. });
  798. const listenText = computed(() => {
  799. if (data.fingeringMode === "fingeringMode") {
  800. if (playStatus.action) {
  801. return "换一换";
  802. } else {
  803. return "开始练习";
  804. }
  805. } else if (data.fingeringMode === "listenMode") {
  806. if (playStatus.action) {
  807. return "再听一遍";
  808. } else {
  809. return "开始听音";
  810. }
  811. }
  812. return "开始听音";
  813. });
  814. const modeText = computed(() => {
  815. let text = "";
  816. let icon = icons.icon_mode;
  817. data.fingeringModeList.forEach((item: any) => {
  818. if (item.value === data.fingeringMode) {
  819. text = item.text;
  820. icon = item.icon;
  821. }
  822. });
  823. return {
  824. text,
  825. icon,
  826. };
  827. });
  828. // 屏幕方向 0 竖,1 横
  829. const orientationDirection = computed(() => {
  830. return ["hulusi-flute", "piccolo", "baroque-recorder"].includes(data.subject) ? 1 : 0;
  831. });
  832. const resultImg = (note: any) => {
  833. if (data.realKey === note.realKey && !playStatus.action) {
  834. return {
  835. icon: icons.icon_btn_ylow,
  836. status: false,
  837. };
  838. } else if (playAction.exampleAnser.realKey === note.realKey) {
  839. return {
  840. icon: icons.icon_btn_ylow,
  841. status: false,
  842. };
  843. } else if (playAction.standardAnswer.realKey === note.realKey) {
  844. // 没有开始答题
  845. if (!playStatus.action) {
  846. return {
  847. icon: icons.icon_btn_ylow,
  848. status: false,
  849. };
  850. }
  851. // 显示答案中
  852. if (playAction.showAnswerLoading) {
  853. return {
  854. icon: icons.icon_btn_green,
  855. status: true,
  856. };
  857. }
  858. // 用户答对
  859. if (playAction.userAnswerStatus === 1) {
  860. return {
  861. icon: icons.icon_btn_green,
  862. status: true,
  863. };
  864. }
  865. } else {
  866. // 用户答错
  867. if (playAction.userAnswerStatus === 2 && playAction.userAnswer.realKey === note.realKey) {
  868. return {
  869. icon: icons.icon_btn_red,
  870. status: true,
  871. };
  872. }
  873. }
  874. return {
  875. icon: icons.icon_btn_blue,
  876. status: true,
  877. };
  878. };
  879. return () => {
  880. const relationship = fingerData.subject?.relationship?.[data.realKey] || [];
  881. const rs: number[] = Array.isArray(relationship[1]) ? relationship[fingerData.relationshipIndex] : relationship;
  882. const canTizhi = Array.isArray(relationship[1]);
  883. return (
  884. <div class={[styles.fingerBox, state.platform !== IPlatform.PC && !query.modelType && fingerData.fingeringInfo.orientation === 1 ? styles.fingerBottom : styles.fingerRight]}>
  885. <div
  886. class={styles.head}
  887. style={{
  888. paddingTop: data.paddingTop ? data.paddingTop : "",
  889. paddingLeft: data.paddingLeft ? data.paddingLeft : "",
  890. }}
  891. >
  892. <div class={styles.left}>
  893. <button class={[styles.backBtn]} onClick={() => handleBack()}>
  894. <img src={icons.icon_back} />
  895. </button>
  896. <div
  897. class={styles.baseBtn}
  898. onClick={(e) => {
  899. //
  900. // 播放音阶时不能切换
  901. if (playStatus.gamut) {
  902. return;
  903. }
  904. // 开始答题不能切换
  905. if (playAction.listenLock) {
  906. return;
  907. }
  908. data.changeSubjectShow = true;
  909. }}
  910. >
  911. <img src={icons.icon_change_instrument} />
  912. <span>切换乐器</span>
  913. </div>
  914. <div class={styles.baseBtn} onClick={onChangeFingeringModel}>
  915. <img src={modeText.value.icon} />
  916. <span>{modeText.value.text}</span>
  917. </div>
  918. {/* */}
  919. </div>
  920. {/* */}
  921. </div>
  922. <div class={styles.fingerContent}>
  923. <div class={styles.wrapFinger}>
  924. <div
  925. id="fingeringContainer"
  926. class={styles.boxFinger}
  927. style={{
  928. paddingTop: containerBox.value.paddingTop,
  929. paddingBottom: containerBox.value.paddingBottom,
  930. }}
  931. >
  932. <div
  933. style={{
  934. transform: `translate3d(${data.transform.x}px,${data.transform.y}px,0px) scale(${data.transform.scale})`,
  935. transition: data.transform.transition,
  936. }}
  937. class={[styles.fingeringContainer]}
  938. >
  939. <div class={styles.imgs}>
  940. {!data.loadingImg && <img src={data.fingeringMode === "scaleMode" ? fingerData.subject?.json?.full : fingerData.subject?.json?.full1} />}
  941. {rs.map((key: number | string, index: number) => {
  942. const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
  943. return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
  944. })}
  945. <div style={{ left: data.viewIndex == 2 ? "0" : "64%" }} class={[styles.tizhi, canTizhi && styles.canDisplay]} onClick={() => (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)}>
  946. 替指
  947. </div>
  948. <div id="finger-note-2" style={{ left: "50%", transform: "translateX(-50%)" }} class={styles.tizhi} onClick={() => (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)}></div>
  949. </div>
  950. </div>
  951. </div>
  952. <div
  953. class={styles.notes}
  954. style={{
  955. paddingLeft: data.paddingLeft ? data.paddingLeft : "",
  956. }}
  957. >
  958. {playAction.listenTipsStatus && <div class={[styles.tipsT, data.fingeringMode === "fingeringMode" ? styles.playTips2 : styles.playTips]}></div>}
  959. {playAction.userAnswerStatus === 1 && <div class={[styles.tipsT, styles.playSuccess]}></div>}
  960. {playAction.userAnswerStatus === 2 && <div class={[styles.tipsT, styles.playError]}></div>}
  961. {playAction.resetAction && <div class={[styles.tipsT, styles.playTips5]}></div>}
  962. {((data.noteType !== "#c" && (orientationDirection.value === 0 || (orientationDirection.value === 1 && state.platform === IPlatform.PC))) || (orientationDirection.value === 1 && state.platform === IPlatform.APP)) && (
  963. <Button class={styles.noteBtn} onClick={() => scrollNoteBox("left")}>
  964. <Icon name="arrow-left" />
  965. </Button>
  966. )}
  967. <div class={[styles.noteContent, data.fingeringMode !== "scaleMode" && orientationDirection.value === 0 && styles.noteContentOther, browsInfo.ios ? "" : styles.noteContentWrap, data.huaweiPad && styles.huaweiPad]}>
  968. {/* 判断是否为音阶模式 */}
  969. {data.fingeringMode !== "scaleMode" && (
  970. <div draggable={false} class={styles.note} onClick={noteChangeShow}>
  971. <img draggable={false} src={data.noteType === "all" ? icons.icon_btn_orange : icons.icon_btn_orange2} />
  972. </div>
  973. )}
  974. {/* [styles.noteContent, browsInfo.ios ? "" : styles.noteContentWrap, data.huaweiPad && styles.huaweiPad] */}
  975. <div class={styles.lastNoteContent}>
  976. <div ref={noteBoxRef} class={styles.noteBox}>
  977. {data.notes.map((note: IFIGNER_INSTRUMENT_Note, index: number) => {
  978. const steps = new Array(Math.abs(note.step)).fill(1);
  979. return (
  980. <div
  981. id={index == 0 ? "finger-note-0" : ""}
  982. draggable={false}
  983. class={[styles.note, "note-class"]}
  984. key={note.realKey}
  985. onClick={async () => {
  986. // 判断是否在播放音阶
  987. if (playStatus.gamut) return;
  988. if (playAction.listenLock) return;
  989. if (playAction.showAnswerLoading) return;
  990. if (playStatus.action) {
  991. playAction.userAnswer = note;
  992. // 判断用户答题
  993. const userResult = note.realKey === playAction.standardAnswer.realKey ? 1 : 2;
  994. playAction.userAnswerStatus = userResult;
  995. playAction.listenLock = true;
  996. data.realKey = note.realKey;
  997. await fingeringPlay(note, 1000);
  998. resetMode(userResult === 1 ? true : false, 0);
  999. data.realKey = 0;
  1000. // 如果是指法模式显示完之后要还原
  1001. if (data.fingeringMode === "fingeringMode" && userResult === 2) {
  1002. // 延迟显示,因为重置的时候有一个异步操作
  1003. setTimeout(() => {
  1004. data.realKey = playAction.standardAnswer.realKey;
  1005. }, 10);
  1006. }
  1007. playAction.listenLock = false;
  1008. } else {
  1009. noteClick(note);
  1010. }
  1011. }}
  1012. >
  1013. <img draggable={false} src={resultImg(note).icon} />
  1014. {playStatus.action && ((playAction.showAnswerLoading && playAction.standardAnswer.realKey === note.realKey) || (playAction.userAnswerStatus === 1 && playAction.userAnswer.realKey === note.realKey)) ? <span class={styles.showAnswer}></span> : ""}
  1015. {playStatus.action && playAction.userAnswerStatus === 2 && playAction.userAnswer.realKey === note.realKey ? <span class={[styles.showAnswer, styles.errorAnswer]}></span> : ""}
  1016. <div
  1017. class={[
  1018. styles.noteKey,
  1019. ((data.realKey === note.realKey && !playStatus.action) ||
  1020. (playStatus.action && playAction.exampleAnser.realKey === note.realKey) ||
  1021. (playStatus.action && ((playAction.showAnswerLoading && playAction.standardAnswer.realKey === note.realKey) || (playAction.userAnswerStatus === 1 && playAction.userAnswer.realKey === note.realKey))) ||
  1022. (playStatus.action && playAction.userAnswerStatus === 2 && playAction.userAnswer.realKey === note.realKey)) &&
  1023. styles.keyActive,
  1024. ]}
  1025. >
  1026. {/* 显示对应的点 */}
  1027. {note.step > 0 ? steps.map((n: any) => <span class={styles.dot}></span>) : null}
  1028. <div class={styles.noteName}>
  1029. <sup>{note.mark && (note.mark === "rise" ? "#" : "b")}</sup>
  1030. {note.key}
  1031. </div>
  1032. {/* 显示对应的点 */}
  1033. {note.step < 0 ? steps.map((n: any) => <span class={styles.dot}></span>) : null}
  1034. </div>
  1035. </div>
  1036. );
  1037. })}
  1038. </div>
  1039. </div>
  1040. </div>
  1041. {((data.noteType !== "#c" && (orientationDirection.value === 0 || (orientationDirection.value === 1 && state.platform === IPlatform.PC))) || (orientationDirection.value === 1 && state.platform === IPlatform.APP)) && (
  1042. <Button class={styles.noteBtn} onClick={() => scrollNoteBox("right")}>
  1043. <Icon name="arrow" />
  1044. </Button>
  1045. )}
  1046. </div>
  1047. {data.fingeringMode !== "scaleMode" && (
  1048. <div class={styles.optionBtns}>
  1049. <Button class={[styles.oBtn, styles.gamut, playStatus.action && styles.disabled]} round onClick={onGamutPlayOrPause}>
  1050. {playStatus.gamut ? "暂停" : "播放音阶"}
  1051. </Button>
  1052. <Button class={[styles.oBtn, styles.play, playStatus.gamut && styles.disabled]} round onClick={onActionPlay}>
  1053. {listenText.value}
  1054. </Button>
  1055. <Button class={[styles.oBtn, styles.success, !playStatus.answer && styles.disabled]} round onClick={onShowAnswer}>
  1056. 显示答案
  1057. </Button>
  1058. </div>
  1059. )}
  1060. </div>
  1061. <div class={[styles.tips, data.loadingDom ? styles.hiddens : "", data.tipShow ? "" : styles.tipHidden]}>
  1062. <div class={styles.tipTitle}>
  1063. <div class={styles.tipTitleName}>{fingerData.fingeringInfo.code}使用说明</div>
  1064. <Button class={styles.tipClose} onClick={() => (data.tipShow = false)}>
  1065. <Icon name="cross" size={19} color="#fff" />
  1066. </Button>
  1067. </div>
  1068. <div class={styles.iconBook}></div>
  1069. <div class={styles.tipContentbox}>
  1070. <div class={styles.tipContent}>
  1071. {data.tips.map((tip, tipIndex) => (
  1072. <div class={styles.tipItem}>
  1073. <div class={styles.iconWrap}>
  1074. <div class={styles.tipItemIcon}>{tipIndex + 1}</div>
  1075. </div>
  1076. <div>
  1077. {tip.name}: {tip.realName}
  1078. </div>
  1079. </div>
  1080. ))}
  1081. </div>
  1082. </div>
  1083. </div>
  1084. {data.loadingSoundFonts && (
  1085. <div class={styles.loading}>
  1086. <div class={styles.loadingWrap}>
  1087. <img class={styles.loadingIcon} src={icon_loading_img} />
  1088. <Progress percentage={data.loadingSoundProgress} />
  1089. <div class={styles.loadingTip}>加载中,请稍后…</div>
  1090. </div>
  1091. </div>
  1092. )}
  1093. </div>
  1094. <div class={styles.fixedRightBtns}>
  1095. <div class={styles.rightBtn}>
  1096. {data.subject !== "melodica" && data.fingeringMode === "scaleMode" && (
  1097. <div
  1098. class={styles.baseBtn}
  1099. onClick={() => {
  1100. data.viewIndex++;
  1101. if (data.viewIndex > data.viewTotal) {
  1102. if (["pan-flute", "ocarina"].includes(data.subject)) {
  1103. data.viewIndex = 1;
  1104. } else {
  1105. data.viewIndex = 0;
  1106. }
  1107. }
  1108. getFingeringData();
  1109. }}
  1110. >
  1111. <img src={icons.icon_toggle} />
  1112. <span>视图</span>
  1113. </div>
  1114. )}
  1115. <div
  1116. class={styles.baseBtn}
  1117. onClick={() => {
  1118. resetElement();
  1119. data.tipShow = !data.tipShow;
  1120. }}
  1121. >
  1122. <img src={icons.icon_2_1} />
  1123. <span>说明</span>
  1124. </div>
  1125. {!!data.tones.length && data.fingeringMode === "scaleMode" && (
  1126. <>
  1127. {fingerData.fingeringInfo.name == "hulusi-flute" ? (
  1128. <div id="finger-note-1" class={[styles.baseBtn, styles.toggleBtnhulusi, styles.active]} onClick={() => (data.tnoteShow = true)}>
  1129. <div>
  1130. 全按作
  1131. <div class={[styles.noteKey, styles.noteKeyBtn]}>
  1132. {data.activeTone.step > 0 ? <span class={styles.dot}></span> : null}
  1133. <span class={styles.dot}></span>
  1134. <div class={styles.noteName}>
  1135. <sup>{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}</sup>
  1136. {data.activeTone.key}
  1137. </div>
  1138. {data.activeTone.step < 0 ? <span class={styles.dot}></span> : null}
  1139. </div>
  1140. </div>
  1141. <img src={icons.icon_arrow} />
  1142. </div>
  1143. ) : (
  1144. <div id="finger-note-1" class={[styles.baseBtn, styles.toggleBtnhulusi2, styles.active]} onClick={() => (data.tnoteShow = true)}>
  1145. <div class={styles.oterhD}>
  1146. <div>
  1147. <div style={{ marginTop: "-4px" }}>
  1148. <sup>{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}</sup>
  1149. {data.activeTone.name}
  1150. </div>
  1151. </div>
  1152. <img src={icons.icon_arrow} />
  1153. </div>
  1154. </div>
  1155. )}
  1156. </>
  1157. )}
  1158. </div>
  1159. <div class={[styles.baseBtn, !instrumentTranstion.value && styles.resetBtn]} style={{ marginTop: "8px" }} onClick={() => resetElement()}>
  1160. <img src={icons.icon_2_0} />
  1161. <span>还原</span>
  1162. </div>
  1163. </div>
  1164. <Popup class="tonePopup" v-model:show={data.tnoteShow} position={state.platform !== IPlatform.PC && !query.modelType && fingerData.fingeringInfo.orientation === 1 ? "bottom" : "right"}>
  1165. <div class={styles.tones}>
  1166. <div class={styles.toneTitle}>
  1167. <div class={styles.tipTitleName}>移调</div>
  1168. <Button class={styles.tipClose} onClick={() => (data.tnoteShow = false)}>
  1169. <Icon name="cross" size={19} color="#fff" />
  1170. </Button>
  1171. </div>
  1172. <div class={styles.tipContentbox}>
  1173. <div class={styles.tipContent}>
  1174. <div class={styles.tipWrap}>
  1175. <Space size={0} class={styles.toneContent}>
  1176. {data.tones.map((tone: IFIGNER_INSTRUMENT_Note) => {
  1177. const steps = new Array(Math.abs(tone.step)).fill(1);
  1178. return (
  1179. <Button
  1180. class={[fingerData.fingeringInfo.name == "hulusi-flute" && styles.hulusiBtn]}
  1181. round
  1182. plain
  1183. type={data.popupActiveTone.realName === tone.realName ? "primary" : "default"}
  1184. onClick={() => {
  1185. data.popupActiveTone = tone;
  1186. setNotes();
  1187. }}
  1188. >
  1189. {fingerData.fingeringInfo.name == "hulusi-flute" ? (
  1190. <div style={{ display: "flex", alignItems: "center" }}>
  1191. 全按作
  1192. <div class={[styles.noteKey, styles.hulusiNoteKey]}>
  1193. {tone.step > 0 ? <span class={styles.dot}></span> : null}
  1194. <div class={styles.noteName} style={{ fontSize: "0.25rem" }}>
  1195. <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
  1196. {tone.key}
  1197. </div>
  1198. {tone.step < 0 ? <span class={styles.dot}></span> : null}
  1199. </div>
  1200. </div>
  1201. ) : (
  1202. <div class={styles.noteName}>
  1203. <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
  1204. {tone.name}
  1205. </div>
  1206. )}
  1207. </Button>
  1208. );
  1209. })}
  1210. </Space>
  1211. </div>
  1212. <div class={styles.toneAction}>
  1213. <img onClick={() => (data.tnoteShow = false)} src={icons.icon_action_cancel} />
  1214. <img
  1215. onClick={() => {
  1216. data.activeTone = data.popupActiveTone;
  1217. setNotes();
  1218. data.tnoteShow = false;
  1219. }}
  1220. src={icons.icon_action_confirm}
  1221. />
  1222. </div>
  1223. </div>
  1224. </div>
  1225. </div>
  1226. </Popup>
  1227. <Popup v-model:show={data.changeSubjectShow} class={styles.changeSubjectPopup}>
  1228. <ChangeSubject
  1229. subjectList={data.subjects}
  1230. subject={data.subject}
  1231. onClose={() => (data.changeSubjectShow = false)}
  1232. onConfirm={(code: any) => {
  1233. if (data.subject === code) return;
  1234. const originalSubject = JSON.parse(JSON.stringify(data.subject));
  1235. data.subject = code;
  1236. data.viewIndex = 0;
  1237. data.tipShow = false;
  1238. data.loadingDom = true;
  1239. fingerData.fingeringInfo = subjectFingering(data.subject);
  1240. data.activeTone = {} as any;
  1241. resetElement();
  1242. resetMode(true, 0);
  1243. api_setRequestedOrientation(orientationDirection.value);
  1244. data.changeSubjectShow = false;
  1245. // 设置屏幕方向
  1246. setTimeout(() => {
  1247. const before = ["hulusi-flute", "piccolo", "baroque-recorder"].includes(originalSubject) ? 1 : 0;
  1248. if (orientationDirection.value !== before) {
  1249. data.paddingTop = "";
  1250. data.paddingLeft = "";
  1251. }
  1252. __init();
  1253. }, 100);
  1254. }}
  1255. />
  1256. </Popup>
  1257. {props.show && !data.loading && !data.loadingSoundFonts && <GuideIndex fingeringMode={data.fingeringMode} showGuide={false} list={["finger"]} />}
  1258. </div>
  1259. );
  1260. };
  1261. },
  1262. });