index.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import { PropType, computed, defineComponent, nextTick, onBeforeMount, onMounted, reactive } from "vue";
  2. import styles from "./index.module.less";
  3. import icons from "./image/icons.json";
  4. import { FIGNER_INSTRUMENT_DATA, IFIGNER_INSTRUMENT_Note } from "/src/view/figner-preview";
  5. import {
  6. ITypeFingering,
  7. IVocals,
  8. getFingeringConfig,
  9. subjectFingering,
  10. } from "/src/view/fingering/fingering-config";
  11. import { Howl } from "howler";
  12. import { storeData } from "/src/store";
  13. import { api_back } from "/src/helpers/communication";
  14. import Hammer from "hammerjs";
  15. export default defineComponent({
  16. name: "viewFigner",
  17. emits: ["close"],
  18. props: {
  19. isComponent: {
  20. type: Boolean,
  21. default: false,
  22. },
  23. subject: {
  24. type: String as PropType<IVocals>,
  25. default: "",
  26. },
  27. },
  28. setup(props, { emit }) {
  29. const subject = props.subject || "pan-flute";
  30. const data = reactive({
  31. subject: subject,
  32. realKey: 0,
  33. notes: [] as IFIGNER_INSTRUMENT_Note[],
  34. tones: [] as IFIGNER_INSTRUMENT_Note[],
  35. activeTone: "",
  36. soundFonts: {} as any,
  37. viewIndex: 0,
  38. noteAudio: null as unknown as Howl,
  39. transform: {
  40. scale: 1,
  41. x: 0,
  42. y: 0,
  43. startScale: 1,
  44. startX: 0,
  45. startY: 0,
  46. transition: "",
  47. },
  48. });
  49. const fingerData = reactive({
  50. relationshipIndex: 0,
  51. subject: null as unknown as ITypeFingering,
  52. fingeringInfo: subjectFingering(data.subject),
  53. });
  54. const getNotes = () => {
  55. const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
  56. if (fignerData) {
  57. data.tones = fignerData.tones || [];
  58. if (data.tones.length) {
  59. data.activeTone = data.tones[0].realName;
  60. }
  61. setNotes();
  62. }
  63. };
  64. const setNotes = () => {
  65. const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
  66. if (fignerData) {
  67. data.notes = fignerData[`list${data.activeTone}`];
  68. }
  69. };
  70. const getFingeringData = async () => {
  71. const subject: any = data.subject + (data.viewIndex === 0 ? "" : data.viewIndex);
  72. // console.log("🚀 ~ subject:", subject);
  73. fingerData.subject = await getFingeringConfig(subject);
  74. };
  75. const getSounFonts = () => {
  76. const pathname = /(192|localhost)/.test(location.origin) ? "/" : location.pathname;
  77. for (let i = 0; i < data.notes.length; i++) {
  78. const note = data.notes[i];
  79. // console.log("🚀 ~ note:", i)
  80. let url = `${pathname}soundfonts/${data.subject}/`;
  81. url += note.realName;
  82. url += ".mp3";
  83. const noteAudio = new Howl({
  84. src: url,
  85. loop: true,
  86. });
  87. data.soundFonts[note.realKey] = noteAudio;
  88. }
  89. // console.log("🚀 ~ data.soundFonts:", data.soundFonts);
  90. };
  91. onBeforeMount(() => {
  92. getNotes();
  93. getFingeringData();
  94. getSounFonts();
  95. });
  96. const noteClick = (item: IFIGNER_INSTRUMENT_Note) => {
  97. if (data.noteAudio) {
  98. data.noteAudio.stop();
  99. if (data.realKey === item.realKey) {
  100. data.realKey = 0;
  101. data.noteAudio = null as unknown as Howl;
  102. return;
  103. }
  104. }
  105. data.realKey = item.realKey;
  106. data.noteAudio = data.soundFonts[item.realKey];
  107. data.noteAudio.play();
  108. };
  109. /** 返回 */
  110. const handleBack = () => {
  111. if (data.noteAudio) {
  112. data.noteAudio.stop();
  113. data.realKey = 0;
  114. data.noteAudio = null as unknown as Howl;
  115. }
  116. if (props.isComponent) {
  117. console.log("关闭");
  118. emit("close");
  119. return;
  120. }
  121. // 不在APP中,
  122. if (!storeData.isApp) {
  123. window.close();
  124. return;
  125. }
  126. api_back();
  127. };
  128. onMounted(() => {
  129. loadElement();
  130. });
  131. const loadElement = () => {
  132. const fingeringContainer = document.getElementById("fingeringContainer");
  133. // console.log("🚀 ~ fingeringContainer:", fingeringContainer);
  134. const mc = new Hammer.Manager(fingeringContainer as HTMLElement);
  135. mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
  136. mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get("pan")]);
  137. // mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
  138. // mc.get("pinch").set({ enable: true });
  139. mc.on("panstart pinchstart", function (ev) {
  140. data.transform.transition = "";
  141. });
  142. mc.on("panmove pinchmove", function (ev) {
  143. if (ev.type === "pinchmove") {
  144. // console.log("🚀 ~ ev:", ev.type, ev.scale, ev.deltaX, ev.deltaY);
  145. data.transform.scale = ev.scale * data.transform.startScale;
  146. data.transform.x = data.transform.startX + ev.deltaX;
  147. data.transform.y = data.transform.startY + ev.deltaY;
  148. }
  149. if (ev.type === "panmove") {
  150. // console.log("🚀 ~ ev:", ev.type, ev.deltaX, ev.deltaY);
  151. data.transform.x = data.transform.startX + ev.deltaX;
  152. data.transform.y = data.transform.startY + ev.deltaY;
  153. }
  154. });
  155. //
  156. mc.on("hammer.input", function (ev) {
  157. // console.log("🚀 ~ ev:", ev.type, ev.isFinal);
  158. if (ev.isFinal) {
  159. data.transform.startScale = data.transform.scale;
  160. data.transform.startX = data.transform.x;
  161. data.transform.startY = data.transform.y;
  162. }
  163. });
  164. };
  165. const resetElement = () => {
  166. data.transform.transition = "all 0.3s";
  167. nextTick(() => {
  168. data.transform.scale = 1;
  169. data.transform.x = 0;
  170. data.transform.y = 0;
  171. data.transform.startScale = 1;
  172. data.transform.startX = 0;
  173. data.transform.startY = 0;
  174. });
  175. };
  176. return () => {
  177. const relationship = fingerData.subject?.relationship?.[data.realKey] || [];
  178. const rs: number[] = Array.isArray(relationship[1])
  179. ? relationship[fingerData.relationshipIndex]
  180. : relationship;
  181. const canTizhi = Array.isArray(relationship[1]);
  182. return (
  183. <div class={styles.fingerBox}>
  184. <div class={styles.head}>
  185. <div class={styles.left}>
  186. <button
  187. class={[styles.backBtn, data.subject === "pan-flute" && styles.backRight]}
  188. onClick={() => handleBack()}
  189. >
  190. <img src={icons.icon_back} />
  191. </button>
  192. {data.subject === "pan-flute" && (
  193. <div
  194. class={styles.baseBtn}
  195. onClick={() => {
  196. data.viewIndex++;
  197. if (data.viewIndex > 2) {
  198. data.viewIndex = 0;
  199. }
  200. getFingeringData();
  201. }}
  202. >
  203. 切换视图
  204. </div>
  205. )}
  206. </div>
  207. <div class={styles.rightBtn}>
  208. <div class={[styles.item]} onClick={() => resetElement()}>
  209. <img src={icons.icon_2_0} />
  210. <span>还原</span>
  211. </div>
  212. <div class={[styles.item]}>
  213. <img src={icons.icon_2_1} />
  214. <span>小技巧</span>
  215. </div>
  216. </div>
  217. </div>
  218. <div id="fingeringContainer" class={styles.fingerContent}>
  219. <div
  220. style={{
  221. transform: `translate3d(${data.transform.x}px,${data.transform.y}px,0px) scale(${data.transform.scale})`,
  222. transition: data.transform.transition,
  223. }}
  224. class={[styles.fingeringContainer]}
  225. >
  226. <div class={styles.imgs}>
  227. <img src={fingerData.subject?.json?.full} />
  228. {rs.map((key: number | string, index: number) => {
  229. const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
  230. return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
  231. })}
  232. </div>
  233. <div
  234. class={[styles.tizhi, canTizhi && styles.canDisplay]}
  235. onClick={() =>
  236. (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
  237. }
  238. >
  239. 替指
  240. </div>
  241. </div>
  242. <div class={styles.tones} style={{display: data.tones.length ? '' : 'none'}}>
  243. {data.tones.map((tone: IFIGNER_INSTRUMENT_Note) => {
  244. const steps = new Array(Math.abs(tone.step)).fill(1);
  245. return (
  246. <div
  247. draggable={false}
  248. class={styles.note}
  249. onClick={() => {
  250. data.activeTone = tone.realName;
  251. setNotes();
  252. }}
  253. >
  254. {data.activeTone === tone.realName ? (
  255. <img draggable={false} src={icons.icon_btn_ylow} />
  256. ) : (
  257. <img draggable={false} src={icons.icon_btn_blue} />
  258. )}
  259. <div class={[styles.noteKey, data.activeTone === tone.realName && styles.keyActive]}>
  260. {tone.step > 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
  261. <div class={styles.noteName}>
  262. <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
  263. {tone.key}
  264. </div>
  265. {tone.step < 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
  266. </div>
  267. </div>
  268. );
  269. })}
  270. </div>
  271. </div>
  272. <div class={styles.notes}>
  273. {/* <Button>
  274. <Icon name="arrow-left" />
  275. </Button> */}
  276. <div class={styles.noteContent}>
  277. <div class={styles.noteBox}>
  278. {data.notes.map((note: IFIGNER_INSTRUMENT_Note) => {
  279. const steps = new Array(Math.abs(note.step)).fill(1);
  280. return (
  281. <div draggable={false} class={styles.note} onClick={() => noteClick(note)}>
  282. {data.realKey === note.realKey ? (
  283. <img draggable={false} src={icons.icon_btn_ylow} />
  284. ) : (
  285. <img draggable={false} src={icons.icon_btn_blue} />
  286. )}
  287. <div class={[styles.noteKey, data.realKey === note.realKey && styles.keyActive]}>
  288. {note.step > 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
  289. <div class={styles.noteName}>
  290. <sup>{note.mark && (note.mark === "rise" ? "#" : "b")}</sup>
  291. {note.key}
  292. </div>
  293. {note.step < 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
  294. </div>
  295. </div>
  296. );
  297. })}
  298. </div>
  299. </div>
  300. {/* <Button size="small" >
  301. <Icon name="arrow" />
  302. </Button> */}
  303. </div>
  304. </div>
  305. );
  306. };
  307. },
  308. });