index.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. import {
  2. PropType,
  3. computed,
  4. defineComponent,
  5. nextTick,
  6. onBeforeMount,
  7. onMounted,
  8. onUnmounted,
  9. reactive,
  10. ref,
  11. } from "vue";
  12. import styles from "./index.module.less";
  13. import icons from "./image/icons.json";
  14. import { FIGNER_INSTRUMENT_DATA, IFIGNER_INSTRUMENT_Note } from "/src/view/figner-preview";
  15. import {
  16. ITypeFingering,
  17. IVocals,
  18. getFingeringConfig,
  19. mappingVoicePart,
  20. subjectFingering,
  21. } from "/src/view/fingering/fingering-config";
  22. import { Howl } from "howler";
  23. import { storeData } from "/src/store";
  24. import {
  25. api_back,
  26. api_cloudLoading,
  27. api_setRequestedOrientation,
  28. api_setStatusBarVisibility,
  29. isSpecialShapedScreen,
  30. } from "/src/helpers/communication";
  31. import Hammer from "hammerjs";
  32. import { Button, Icon, Loading, Popup, Progress, Space } from "vant";
  33. import GuideIndex from "./guide/guide-index";
  34. import { getQuery } from "/src/utils/queryString";
  35. import { browser } from "/src/utils";
  36. import { usePageVisibility } from "@vant/use";
  37. import { watch } from "vue";
  38. import icon_loading_img from "./image/icon_loading_img.png";
  39. import state, { IPlatform } from "/src/state";
  40. export default defineComponent({
  41. name: "viewFigner",
  42. emits: ["close"],
  43. props: {
  44. show: {
  45. type: Boolean,
  46. default: true,
  47. },
  48. isComponent: {
  49. type: Boolean,
  50. default: false,
  51. },
  52. subject: {
  53. type: String as PropType<IVocals>,
  54. default: "",
  55. },
  56. },
  57. setup(props, { emit }) {
  58. const query = getQuery();
  59. const browsInfo = browser();
  60. const code = mappingVoicePart(query.code, "INSTRUMENT");
  61. const subject = props.isComponent ? props.subject || "pan-flute" : code || "pan-flute";
  62. const data = reactive({
  63. loading: true,
  64. subject: subject as any,
  65. realKey: 0,
  66. notes: [] as IFIGNER_INSTRUMENT_Note[],
  67. tones: [] as IFIGNER_INSTRUMENT_Note[],
  68. activeTone: {} as IFIGNER_INSTRUMENT_Note,
  69. popupActiveTone: {} as IFIGNER_INSTRUMENT_Note,
  70. activeToneName: "",
  71. soundFonts: {} as any,
  72. viewIndex: 0,
  73. viewTotal: 1,
  74. noteAudio: null as unknown as Howl,
  75. transform: {
  76. scale: 1,
  77. x: 0,
  78. y: 0,
  79. startScale: 1,
  80. startX: 0,
  81. startY: 0,
  82. transition: "",
  83. },
  84. tipShow: false,
  85. tips: [] as IFIGNER_INSTRUMENT_Note[],
  86. tnoteShow: false,
  87. loadingSoundFonts: true,
  88. loadingSoundProgress: 0,
  89. huaweiPad: navigator?.userAgent?.includes("UAWEIVRD-W09") ? true : false,
  90. paddingTop: "",
  91. paddingLeft: "",
  92. });
  93. const fingerData = reactive({
  94. relationshipIndex: 0,
  95. subject: null as unknown as ITypeFingering,
  96. fingeringInfo: subjectFingering(data.subject),
  97. });
  98. if (!props.isComponent) {
  99. state.fingeringInfo = fingerData.fingeringInfo;
  100. }
  101. const getAPPData = async (type: "top" | "left") => {
  102. const screenData = await isSpecialShapedScreen();
  103. if (screenData?.content) {
  104. // console.log("🚀 ~ screenData:", screenData.content);
  105. const { isSpecialShapedScreen, notchHeight } = screenData.content;
  106. if (isSpecialShapedScreen) {
  107. if (type === "top") {
  108. data.paddingTop = 25 + "px";
  109. }
  110. if (type === "left") {
  111. data.paddingLeft = 25 + "px";
  112. }
  113. }
  114. }
  115. };
  116. const getHeadTop = () => {
  117. if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 1) {
  118. getAPPData("top");
  119. }
  120. if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 0) {
  121. getAPPData("left");
  122. }
  123. }
  124. const getNotes = () => {
  125. const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
  126. if (fignerData) {
  127. data.tones = fignerData.tones || [];
  128. if (data.tones.length) {
  129. data.activeTone = data.tones[0];
  130. data.popupActiveTone = data.tones[0];
  131. }
  132. data.tips = fignerData.tips || [];
  133. setNotes();
  134. setTimeout(() => {
  135. data.loading = false;
  136. }, 600);
  137. }
  138. };
  139. const setNotes = () => {
  140. const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
  141. if (fignerData) {
  142. data.notes = fignerData[`list${data.activeTone.realName || ""}`];
  143. }
  144. };
  145. const getFingeringData = async () => {
  146. const subject: any = data.subject + (data.viewIndex === 0 ? "" : data.viewIndex);
  147. console.log("🚀 ~ subject:", subject);
  148. fingerData.subject = await getFingeringConfig(subject);
  149. };
  150. const createAudio = (url: string) => {
  151. return new Promise((resolve) => {
  152. const noteAudio = new Howl({
  153. src: url,
  154. loop: true,
  155. onload: () => {
  156. resolve(noteAudio);
  157. },
  158. });
  159. });
  160. };
  161. const getSounFonts = async () => {
  162. const pathname = /(192|localhost)/.test(location.origin) ? "/" : location.pathname;
  163. data.loadingSoundFonts = true;
  164. data.loadingSoundProgress = 0;
  165. for (let i = 0; i < data.notes.length; i++) {
  166. const note = data.notes[i];
  167. // console.log("🚀 ~ note:", i);
  168. let url = `${pathname}soundfonts/${data.subject}/`;
  169. url += note.realName;
  170. url += ".mp3";
  171. data.soundFonts[note.realKey] = await createAudio(url);
  172. data.loadingSoundProgress = Math.floor(((i + 1) / data.notes.length) * 100);
  173. }
  174. data.loadingSoundProgress = 100;
  175. api_cloudLoading();
  176. data.loadingSoundFonts = false;
  177. // console.log("🚀 ~ data.soundFonts:", data.soundFonts);
  178. };
  179. onBeforeMount(() => {
  180. getNotes();
  181. if (["pan-flute", "ocarina"].includes(data.subject)) {
  182. data.viewIndex = 1;
  183. }
  184. const o: any = {
  185. "pan-flute": 2,
  186. ocarina: 2,
  187. piccolo: 2,
  188. "hulusi-flute": 2,
  189. };
  190. data.viewTotal = o[data.subject] || 1;
  191. getFingeringData();
  192. getSounFonts();
  193. getHeadTop();
  194. });
  195. const noteClick = (item: IFIGNER_INSTRUMENT_Note) => {
  196. if (data.noteAudio) {
  197. data.noteAudio.stop();
  198. if (data.realKey === item.realKey) {
  199. data.realKey = 0;
  200. data.noteAudio = null as unknown as Howl;
  201. return;
  202. }
  203. }
  204. data.realKey = item.realKey;
  205. data.noteAudio = data.soundFonts[item.realKey];
  206. data.noteAudio.play();
  207. };
  208. const handleStop = () => {
  209. if (data.noteAudio) {
  210. data.noteAudio.stop();
  211. data.realKey = 0;
  212. data.noteAudio = null as unknown as Howl;
  213. }
  214. };
  215. /** 返回 */
  216. const handleBack = () => {
  217. handleStop();
  218. if (props.isComponent) {
  219. console.log("关闭");
  220. emit("close");
  221. return;
  222. } else {
  223. // if (fingerData.fingeringInfo.orientation === 0) {
  224. // api_setRequestedOrientation(1);
  225. // }
  226. }
  227. // 不在APP中,
  228. if (!storeData.isApp) {
  229. window.close();
  230. return;
  231. }
  232. api_back();
  233. };
  234. onMounted(() => {
  235. loadElement();
  236. api_setStatusBarVisibility();
  237. });
  238. const loadElement = () => {
  239. const fingeringContainer = document.getElementById("fingeringContainer");
  240. // console.log("🚀 ~ fingeringContainer:", fingeringContainer);
  241. const mc = new Hammer.Manager(fingeringContainer as HTMLElement);
  242. mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
  243. mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get("pan")]);
  244. // mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
  245. // mc.get("pinch").set({ enable: true });
  246. mc.on("panstart pinchstart", function (ev) {
  247. data.transform.transition = "";
  248. });
  249. mc.on("panmove pinchmove", function (ev) {
  250. if (ev.type === "pinchmove") {
  251. // console.log("🚀 ~ ev:", ev.type, ev.scale, ev.deltaX, ev.deltaY);
  252. data.transform.scale = ev.scale * data.transform.startScale;
  253. data.transform.x = data.transform.startX + ev.deltaX;
  254. data.transform.y = data.transform.startY + ev.deltaY;
  255. }
  256. if (ev.type === "panmove") {
  257. // console.log("🚀 ~ ev:", ev.type, ev.deltaX, ev.deltaY);
  258. data.transform.x = data.transform.startX + ev.deltaX;
  259. data.transform.y = data.transform.startY + ev.deltaY;
  260. }
  261. });
  262. //
  263. mc.on("hammer.input", function (ev) {
  264. // console.log("🚀 ~ ev:", ev.type, ev.isFinal);
  265. if (ev.isFinal) {
  266. data.transform.startScale = data.transform.scale;
  267. data.transform.startX = data.transform.x;
  268. data.transform.startY = data.transform.y;
  269. }
  270. });
  271. };
  272. const resetElement = () => {
  273. data.transform.transition = "all 0.3s";
  274. nextTick(() => {
  275. data.transform.scale = 1;
  276. data.transform.x = 0;
  277. data.transform.y = 0;
  278. data.transform.startScale = 1;
  279. data.transform.startX = 0;
  280. data.transform.startY = 0;
  281. });
  282. };
  283. const pageVisible = usePageVisibility();
  284. watch(
  285. () => pageVisible.value,
  286. (val) => {
  287. if (val === "hidden") {
  288. console.log("页面隐藏停止播放");
  289. handleStop();
  290. }
  291. }
  292. );
  293. /** 课件播放 */
  294. const changePlay = (res: any) => {
  295. if (res?.data?.api === "setPlayState") {
  296. handleStop();
  297. }
  298. };
  299. const noteBoxRef = ref();
  300. const scrollNoteBox = (type: "left" | "right") => {
  301. const width = noteBoxRef.value.offsetWidth / 2;
  302. (noteBoxRef.value as unknown as HTMLElement).scrollBy({
  303. left: type === "left" ? -width : width,
  304. behavior: "smooth",
  305. });
  306. };
  307. /** 滚轮缩放 */
  308. const handleWheel = (e: WheelEvent) => {
  309. e.preventDefault();
  310. if (e.deltaY > 0) {
  311. data.transform.scale -= 0.1;
  312. if (data.transform.scale <= 0.5) {
  313. data.transform.scale = 0.5;
  314. }
  315. } else {
  316. data.transform.scale += 0.1;
  317. if (data.transform.scale >= 2) {
  318. data.transform.scale = 2;
  319. }
  320. }
  321. };
  322. onMounted(() => {
  323. window.addEventListener("message", changePlay);
  324. const fingeringContainer = document.getElementById("fingeringContainer");
  325. fingeringContainer?.addEventListener("wheel", handleWheel);
  326. });
  327. onUnmounted(() => {
  328. window.removeEventListener("message", changePlay);
  329. const fingeringContainer = document.getElementById("fingeringContainer");
  330. fingeringContainer?.removeEventListener("wheel", handleWheel);
  331. });
  332. const containerBox = computed(() => {
  333. if (state.platform === IPlatform.PC || query.modelType) {
  334. return {
  335. paddingTop: "1rem",
  336. paddingBottom: "",
  337. };
  338. }
  339. if (data.subject === "hulusi-flute") {
  340. return {
  341. paddingTop: "3.1rem",
  342. paddingBottom: ".8rem",
  343. };
  344. } else if (data.subject === "piccolo") {
  345. return {
  346. paddingTop: "4rem",
  347. paddingBottom: ".8rem",
  348. };
  349. } else if (data.subject === "pan-flute") {
  350. return {
  351. paddingTop: "0",
  352. paddingBottom: "0",
  353. };
  354. } else if (data.subject === "ocarina") {
  355. return {
  356. paddingTop: "1.2rem",
  357. paddingBottom: "0",
  358. };
  359. } else if (data.subject === "melodica") {
  360. return {
  361. paddingTop: "2.8rem",
  362. paddingBottom: "1.8rem",
  363. };
  364. } else {
  365. return {
  366. paddingTop: "",
  367. paddingBottom: "",
  368. };
  369. }
  370. });
  371. return () => {
  372. const relationship = fingerData.subject?.relationship?.[data.realKey] || [];
  373. const rs: number[] = Array.isArray(relationship[1])
  374. ? relationship[fingerData.relationshipIndex]
  375. : relationship;
  376. const canTizhi = Array.isArray(relationship[1]);
  377. return (
  378. <div
  379. class={[
  380. styles.fingerBox,
  381. state.platform !== IPlatform.PC &&
  382. !query.modelType &&
  383. fingerData.fingeringInfo.orientation === 1
  384. ? styles.fingerBottom
  385. : styles.fingerRight,
  386. ]}
  387. >
  388. <div
  389. class={styles.head}
  390. style={{
  391. paddingTop: data.paddingTop ? data.paddingTop : "",
  392. paddingLeft: data.paddingLeft ? data.paddingLeft : "",
  393. }}
  394. >
  395. <div class={styles.left}>
  396. <button class={[styles.backBtn]} onClick={() => handleBack()}>
  397. <img src={icons.icon_back} />
  398. </button>
  399. {data.subject !== "melodica" && (
  400. <div
  401. class={styles.baseBtn}
  402. onClick={() => {
  403. data.viewIndex++;
  404. if (data.viewIndex > data.viewTotal) {
  405. if (["pan-flute", "ocarina"].includes(data.subject)) {
  406. data.viewIndex = 1;
  407. } else {
  408. data.viewIndex = 0;
  409. }
  410. }
  411. getFingeringData();
  412. }}
  413. >
  414. <img src={icons.icon_toggle} />
  415. <span>切换视图</span>
  416. </div>
  417. )}
  418. </div>
  419. <div class={styles.rightBtn}>
  420. <div class={styles.baseBtn} onClick={() => resetElement()}>
  421. <img src={icons.icon_2_0} />
  422. <span>还原</span>
  423. </div>
  424. <div
  425. class={styles.baseBtn}
  426. onClick={() => {
  427. resetElement();
  428. data.tipShow = !data.tipShow;
  429. }}
  430. >
  431. <img src={icons.icon_2_1} />
  432. <span>使用说明</span>
  433. </div>
  434. </div>
  435. </div>
  436. <div class={styles.fingerContent}>
  437. <div class={styles.wrapFinger}>
  438. <div
  439. id="fingeringContainer"
  440. class={styles.boxFinger}
  441. style={{
  442. paddingTop: containerBox.value.paddingTop,
  443. paddingBottom: containerBox.value.paddingBottom,
  444. }}
  445. >
  446. <div
  447. style={{
  448. transform: `translate3d(${data.transform.x}px,${data.transform.y}px,0px) scale(${data.transform.scale})`,
  449. transition: data.transform.transition,
  450. }}
  451. class={[styles.fingeringContainer]}
  452. >
  453. <div class={styles.imgs}>
  454. <img src={fingerData.subject?.json?.full} />
  455. {rs.map((key: number | string, index: number) => {
  456. const nk: string =
  457. typeof key === "string" ? key.replace("active-", "") : String(key);
  458. return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
  459. })}
  460. <div
  461. style={{ left: data.viewIndex == 2 ? "0" : "64%" }}
  462. class={[styles.tizhi, canTizhi && styles.canDisplay]}
  463. onClick={() =>
  464. (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
  465. }
  466. >
  467. 替指
  468. </div>
  469. <div
  470. id="finger-note-2"
  471. style={{ left: "50%", transform: "translateX(-50%)" }}
  472. class={styles.tizhi}
  473. onClick={() =>
  474. (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
  475. }
  476. ></div>
  477. </div>
  478. </div>
  479. </div>
  480. <div
  481. class={styles.notes}
  482. style={{
  483. paddingLeft: data.paddingLeft ? data.paddingLeft : "",
  484. }}
  485. >
  486. <Button class={styles.noteBtn} onClick={() => scrollNoteBox("left")}>
  487. <Icon name="arrow-left" />
  488. </Button>
  489. <div
  490. class={[
  491. styles.noteContent,
  492. browsInfo.ios ? "" : styles.noteContentWrap,
  493. data.huaweiPad && styles.huaweiPad,
  494. ]}
  495. >
  496. <div ref={noteBoxRef} class={styles.noteBox}>
  497. {data.notes.map((note: IFIGNER_INSTRUMENT_Note, index: number) => {
  498. const steps = new Array(Math.abs(note.step)).fill(1);
  499. return (
  500. <div
  501. id={index == 0 ? "finger-note-0" : ""}
  502. draggable={false}
  503. class={styles.note}
  504. onClick={() => noteClick(note)}
  505. >
  506. {data.realKey === note.realKey ? (
  507. <img draggable={false} src={icons.icon_btn_ylow} />
  508. ) : (
  509. <img draggable={false} src={icons.icon_btn_blue} />
  510. )}
  511. <div
  512. class={[styles.noteKey, data.realKey === note.realKey && styles.keyActive]}
  513. >
  514. {note.step > 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
  515. <div class={styles.noteName}>
  516. <sup>{note.mark && (note.mark === "rise" ? "#" : "b")}</sup>
  517. {note.key}
  518. </div>
  519. {note.step < 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
  520. </div>
  521. </div>
  522. );
  523. })}
  524. </div>
  525. </div>
  526. <Button class={styles.noteBtn} onClick={() => scrollNoteBox("right")}>
  527. <Icon name="arrow" />
  528. </Button>
  529. </div>
  530. </div>
  531. <div class={[styles.tips, data.tipShow ? "" : styles.tipHidden]}>
  532. <div class={styles.tipTitle}>
  533. <div class={styles.tipTitleName}>{fingerData.fingeringInfo.code}使用说明</div>
  534. <Button class={styles.tipClose} onClick={() => (data.tipShow = false)}>
  535. <Icon name="cross" size={19} color="#fff" />
  536. </Button>
  537. </div>
  538. <div class={styles.iconBook}></div>
  539. <div class={styles.tipContentbox}>
  540. <div class={styles.tipContent}>
  541. {data.tips.map((tip, tipIndex) => (
  542. <div class={styles.tipItem}>
  543. <div class={styles.iconWrap}>
  544. <div class={styles.tipItemIcon}>{tipIndex + 1}</div>
  545. </div>
  546. <div>
  547. {tip.name}: {tip.realName}
  548. </div>
  549. </div>
  550. ))}
  551. </div>
  552. </div>
  553. </div>
  554. {data.loadingSoundFonts && (
  555. <div class={styles.loading}>
  556. <div class={styles.loadingWrap}>
  557. <img class={styles.loadingIcon} src={icon_loading_img} />
  558. <Progress percentage={data.loadingSoundProgress} />
  559. <div class={styles.loadingTip}>加载中,请稍后…</div>
  560. </div>
  561. </div>
  562. )}
  563. </div>
  564. {!!data.tones.length && (
  565. <>
  566. {fingerData.fingeringInfo.name == "hulusi-flute" ? (
  567. <div
  568. id="finger-note-1"
  569. class={[styles.toggleBtn, styles.toggleBtnhulusi]}
  570. onClick={() => (data.tnoteShow = true)}
  571. >
  572. <div>
  573. 全按作
  574. <div class={[styles.noteKey]}>
  575. {data.activeTone.step > 0 ? <span class={styles.dot}></span> : null}
  576. <div class={styles.noteName}>
  577. <sup>
  578. {data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}
  579. </sup>
  580. {data.activeTone.key}
  581. </div>
  582. {data.activeTone.step < 0 ? <span class={styles.dot}></span> : null}
  583. </div>
  584. </div>
  585. <img src={icons.icon_arrow} />
  586. </div>
  587. ) : (
  588. <div id="finger-note-1" class={styles.toggleBtn} onClick={() => (data.tnoteShow = true)}>
  589. <div style={{ marginTop: "-4px" }}>
  590. <sup>{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}</sup>
  591. {data.activeTone.name}
  592. </div>
  593. <img src={icons.icon_arrow} />
  594. </div>
  595. )}
  596. </>
  597. )}
  598. <Popup
  599. class="tonePopup"
  600. v-model:show={data.tnoteShow}
  601. position={
  602. state.platform !== IPlatform.PC &&
  603. !query.modelType &&
  604. fingerData.fingeringInfo.orientation === 1
  605. ? "bottom"
  606. : "right"
  607. }
  608. >
  609. <div class={styles.tones}>
  610. <div class={styles.toneTitle}>
  611. <div class={styles.tipTitleName}>移调</div>
  612. <Button class={styles.tipClose} onClick={() => (data.tnoteShow = false)}>
  613. <Icon name="cross" size={19} color="#fff" />
  614. </Button>
  615. </div>
  616. <div class={styles.tipContentbox}>
  617. <div class={styles.tipContent}>
  618. <div class={styles.tipWrap}>
  619. <Space size={0} class={styles.toneContent}>
  620. {data.tones.map((tone: IFIGNER_INSTRUMENT_Note) => {
  621. const steps = new Array(Math.abs(tone.step)).fill(1);
  622. return (
  623. <Button
  624. class={[fingerData.fingeringInfo.name == "hulusi-flute" && styles.hulusiBtn]}
  625. round
  626. plain
  627. type={
  628. data.popupActiveTone.realName === tone.realName ? "primary" : "default"
  629. }
  630. onClick={() => {
  631. data.popupActiveTone = tone;
  632. setNotes();
  633. }}
  634. >
  635. {fingerData.fingeringInfo.name == "hulusi-flute" ? (
  636. <div style={{ display: "flex", alignItems: "center" }}>
  637. 全按作
  638. <div class={[styles.noteKey, styles.hulusiNoteKey]}>
  639. {tone.step > 0 ? <span class={styles.dot}></span> : null}
  640. <div class={styles.noteName} style={{ fontSize: "0.25rem" }}>
  641. <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
  642. {tone.key}
  643. </div>
  644. {tone.step < 0 ? <span class={styles.dot}></span> : null}
  645. </div>
  646. </div>
  647. ) : (
  648. <div class={styles.noteName}>
  649. <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
  650. {tone.name}
  651. </div>
  652. )}
  653. </Button>
  654. );
  655. })}
  656. </Space>
  657. </div>
  658. <div class={styles.toneAction}>
  659. <img onClick={() => (data.tnoteShow = false)} src={icons.icon_action_cancel} />
  660. <img
  661. onClick={() => {
  662. data.activeTone = data.popupActiveTone;
  663. setNotes();
  664. data.tnoteShow = false;
  665. }}
  666. src={icons.icon_action_confirm}
  667. />
  668. </div>
  669. </div>
  670. </div>
  671. </div>
  672. </Popup>
  673. {props.show && !data.loading && !data.loadingSoundFonts && (
  674. <GuideIndex showGuide={false} list={["finger"]} />
  675. )}
  676. </div>
  677. );
  678. };
  679. },
  680. });