Browse Source

feat:增加英式竖笛

TIANYONG 1 year ago
parent
commit
9cfeb606ca

+ 2 - 0
src/page-instrument/view-detail/index.tsx

@@ -90,6 +90,7 @@ export default defineComponent({
       }
     };
     onBeforeMount(() => {
+      console.time('渲染加载耗时')
       api_keepScreenLongLight();
       getAPPData();
       api_setStatusBarVisibility();
@@ -244,6 +245,7 @@ export default defineComponent({
       resetPlaybackToStart();
 
       pushAppMusic();
+      console.timeEnd('渲染加载耗时')
     };
     /** 指法配置 */
     const fingerConfig = computed<any>(() => {

+ 1179 - 687
src/page-instrument/view-figner/index.tsx

@@ -1,35 +1,13 @@
-import {
-	PropType,
-	computed,
-	defineComponent,
-	nextTick,
-	onBeforeMount,
-	onMounted,
-	onUnmounted,
-	reactive,
-	ref,
-} from "vue";
+import { PropType, computed, defineComponent, nextTick, onBeforeMount, onMounted, onUnmounted, reactive, ref } from "vue";
 import styles from "./index.module.less";
 import icons from "./image/icons.json";
-import { FIGNER_INSTRUMENT_DATA, IFIGNER_INSTRUMENT_Note } from "/src/view/figner-preview";
-import {
-	ITypeFingering,
-	IVocals,
-	getFingeringConfig,
-	mappingVoicePart,
-	subjectFingering,
-} from "/src/view/fingering/fingering-config";
+import { FIGNER_INSTRUMENT_DATA, FIGNER_INSTRUMENT_REALKEY, IFIGNER_INSTRUMENT_Note } from "/src/view/figner-preview";
+import { ITypeFingering, IVocals, getFingeringConfig, mappingVoicePart, subjectFingering } from "/src/view/fingering/fingering-config";
 import { Howl } from "howler";
 import { storeData } from "/src/store";
-import {
-	api_back,
-	api_cloudLoading,
-	api_setRequestedOrientation,
-	api_setStatusBarVisibility,
-	isSpecialShapedScreen,
-} from "/src/helpers/communication";
+import { api_back, api_cloudLoading, api_setRequestedOrientation, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
 import Hammer from "hammerjs";
-import { Button, Icon, Loading, Popup, Progress, Space } from "vant";
+import { Button, Icon, Loading, Popover, Popup, Progress, Space, showToast } from "vant";
 import GuideIndex from "./guide/guide-index";
 import { getQuery } from "/src/utils/queryString";
 import { browser } from "/src/utils";
@@ -37,666 +15,1180 @@ import { usePageVisibility } from "@vant/use";
 import { watch } from "vue";
 import icon_loading_img from "./image/icon_loading_img.png";
 import state, { IPlatform } from "/src/state";
+import { getSubjectList } from "../api";
 
 export default defineComponent({
-	name: "viewFigner",
-	emits: ["close"],
-	props: {
-		show: {
-			type: Boolean,
-			default: true,
-		},
-		isComponent: {
-			type: Boolean,
-			default: false,
-		},
-		subject: {
-			type: String as PropType<IVocals>,
-			default: "",
-		},
-	},
-	setup(props, { emit }) {
-		const query = getQuery();
-		const browsInfo = browser();
-		const code = mappingVoicePart(query.code, "INSTRUMENT");
-		const subject = props.isComponent ? props.subject || "pan-flute" : code || "pan-flute";
-		const data = reactive({
-			loading: true,
-			subject: subject as any,
-			realKey: 0,
-			notes: [] as IFIGNER_INSTRUMENT_Note[],
-			tones: [] as IFIGNER_INSTRUMENT_Note[],
-			activeTone: {} as IFIGNER_INSTRUMENT_Note,
-			popupActiveTone: {} as IFIGNER_INSTRUMENT_Note,
-			activeToneName: "",
-			soundFonts: {} as any,
-			viewIndex: 0,
-			viewTotal: 1,
-			noteAudio: null as unknown as Howl,
-			transform: {
-				scale: 1,
-				x: 0,
-				y: 0,
-				startScale: 1,
-				startX: 0,
-				startY: 0,
-				transition: "",
-			},
-			tipShow: false,
-			tips: [] as IFIGNER_INSTRUMENT_Note[],
-
-			tnoteShow: false,
-			loadingSoundFonts: true,
-			loadingSoundProgress: 0,
-
-			huaweiPad: navigator?.userAgent?.includes("UAWEIVRD-W09") ? true : false,
-			paddingTop: "",
-			paddingLeft: "",
-		});
-		const fingerData = reactive({
-			relationshipIndex: 0,
-			subject: null as unknown as ITypeFingering,
-			fingeringInfo: subjectFingering(data.subject),
-		});
-		if (!props.isComponent) {
-			state.fingeringInfo = fingerData.fingeringInfo;
-		}
-
-		const getAPPData = async (type: "top" | "left") => {
-			const screenData = await isSpecialShapedScreen();
-			if (screenData?.content) {
-				// console.log("🚀 ~ screenData:", screenData.content);
-				const { isSpecialShapedScreen, notchHeight } = screenData.content;
-				if (isSpecialShapedScreen) {
-					if (type === "top") {
-						data.paddingTop = 25 + "px";
-					}
-					if (type === "left") {
-						data.paddingLeft = 25 + "px";
-					}
-				}
-			}
-		};
-
-		const getHeadTop = () => {
-			if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 1) {
-				getAPPData("top");
-			}
-			if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 0) {
-				getAPPData("left");
-			}
-		}
-
-		const getNotes = () => {
-			const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
-			if (fignerData) {
-				data.tones = fignerData.tones || [];
-				if (data.tones.length) {
-					data.activeTone = data.tones[0];
-					data.popupActiveTone = data.tones[0];
-				}
-				data.tips = fignerData.tips || [];
-				setNotes();
-				setTimeout(() => {
-					data.loading = false;
-				}, 600);
-			}
-		};
-		const setNotes = () => {
-			const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
-			if (fignerData) {
-				data.notes = fignerData[`list${data.activeTone.realName || ""}`];
-			}
-		};
-		const getFingeringData = async () => {
-			const subject: any = data.subject + (data.viewIndex === 0 ? "" : data.viewIndex);
-			console.log("🚀 ~ subject:", subject);
-			fingerData.subject = await getFingeringConfig(subject);
-		};
-		const createAudio = (url: string) => {
-			return new Promise((resolve) => {
-				const noteAudio = new Howl({
-					src: url,
-					loop: true,
-					onload: () => {
-						resolve(noteAudio);
-					},
-				});
-			});
-		};
-		const getSounFonts = async () => {
-			const pathname = /(192|localhost)/.test(location.origin) ? "/" : location.pathname;
-			data.loadingSoundFonts = true;
-			data.loadingSoundProgress = 0;
-			for (let i = 0; i < data.notes.length; i++) {
-				const note = data.notes[i];
-				// console.log("🚀 ~ note:", i);
-				let url = `${pathname}soundfonts/${data.subject}/`;
-				url += note.realName;
-				url += ".mp3";
-				data.soundFonts[note.realKey] = await createAudio(url);
-				data.loadingSoundProgress = Math.floor(((i + 1) / data.notes.length) * 100);
-			}
-			data.loadingSoundProgress = 100;
-			api_cloudLoading();
-			data.loadingSoundFonts = false;
-			// console.log("🚀 ~ data.soundFonts:", data.soundFonts);
-		};
-		onBeforeMount(() => {
-			getNotes();
-			if (["pan-flute", "ocarina"].includes(data.subject)) {
-				data.viewIndex = 1;
-			}
-			const o: any = {
-				"pan-flute": 2,
-				ocarina: 2,
-				piccolo: 2,
-				"hulusi-flute": 2,
-			};
-			data.viewTotal = o[data.subject] || 1;
-			getFingeringData();
-			getSounFonts();
-			getHeadTop();
-		});
-
-		const noteClick = (item: IFIGNER_INSTRUMENT_Note) => {
-			// console.log('音高', item.realKey)
-			if (data.noteAudio) {
-				data.noteAudio.stop();
-				if (data.realKey === item.realKey) {
-					data.realKey = 0;
-					data.noteAudio = null as unknown as Howl;
-					return;
-				}
-			}
-			data.realKey = item.realKey;
-			data.noteAudio = data.soundFonts[item.realKey];
-			data.noteAudio.play();
-		};
-		const handleStop = () => {
-			if (data.noteAudio) {
-				data.noteAudio.stop();
-				data.realKey = 0;
-				data.noteAudio = null as unknown as Howl;
-			}
-		};
-
-		/** 返回 */
-		const handleBack = () => {
-			handleStop();
-			if (props.isComponent) {
-				console.log("关闭");
-				emit("close");
-				return;
-			} else {
-				// if (fingerData.fingeringInfo.orientation === 0) {
-				// 	api_setRequestedOrientation(1);
-				// }
-			}
-			// 不在APP中,
-			if (!storeData.isApp) {
-				window.close();
-				return;
-			}
-			api_back();
-		};
-
-		onMounted(() => {
-			loadElement();
-			api_setStatusBarVisibility();
-		});
-		const loadElement = () => {
-			const fingeringContainer = document.getElementById("fingeringContainer");
-			// console.log("🚀 ~ fingeringContainer:", fingeringContainer);
-			const mc = new Hammer.Manager(fingeringContainer as HTMLElement);
-			mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
-			mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get("pan")]);
-			// mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
-			// mc.get("pinch").set({ enable: true });
-			mc.on("panstart pinchstart", function (ev) {
-				data.transform.transition = "";
-			});
-			mc.on("panmove pinchmove", function (ev) {
-				if (ev.type === "pinchmove") {
-					// console.log("🚀 ~ ev:", ev.type, ev.scale, ev.deltaX, ev.deltaY);
-					data.transform.scale = ev.scale * data.transform.startScale;
-					data.transform.x = data.transform.startX + ev.deltaX;
-					data.transform.y = data.transform.startY + ev.deltaY;
-				}
-				if (ev.type === "panmove") {
-					// console.log("🚀 ~ ev:", ev.type, ev.deltaX, ev.deltaY);
-					data.transform.x = data.transform.startX + ev.deltaX;
-					data.transform.y = data.transform.startY + ev.deltaY;
-				}
-			});
-			//
-			mc.on("hammer.input", function (ev) {
-				// console.log("🚀 ~ ev:", ev.type, ev.isFinal);
-				if (ev.isFinal) {
-					data.transform.startScale = data.transform.scale;
-					data.transform.startX = data.transform.x;
-					data.transform.startY = data.transform.y;
-				}
-			});
-		};
-		const resetElement = () => {
-			data.transform.transition = "all 0.3s";
-			nextTick(() => {
-				data.transform.scale = 1;
-				data.transform.x = 0;
-				data.transform.y = 0;
-				data.transform.startScale = 1;
-				data.transform.startX = 0;
-				data.transform.startY = 0;
-			});
-		};
-
-		const pageVisible = usePageVisibility();
-		watch(
-			() => pageVisible.value,
-			(val) => {
-				if (val === "hidden") {
-					console.log("页面隐藏停止播放");
-					handleStop();
-				}
-			}
-		);
-		/** 课件播放 */
-		const changePlay = (res: any) => {
-			if (res?.data?.api === "setPlayState") {
-				handleStop();
-			}
-		};
-
-		const noteBoxRef = ref();
-		const scrollNoteBox = (type: "left" | "right") => {
-			const width = noteBoxRef.value.offsetWidth / 2;
-			(noteBoxRef.value as unknown as HTMLElement).scrollBy({
-				left: type === "left" ? -width : width,
-				behavior: "smooth",
-			});
-		};
-
-		/** 滚轮缩放 */
-		const handleWheel = (e: WheelEvent) => {
-			e.preventDefault();
-			if (e.deltaY > 0) {
-				data.transform.scale -= 0.1;
-				if (data.transform.scale <= 0.5) {
-					data.transform.scale = 0.5;
-				}
-			} else {
-				data.transform.scale += 0.1;
-				if (data.transform.scale >= 2) {
-					data.transform.scale = 2;
-				}
-			}
-		};
-
-		onMounted(() => {
-			window.addEventListener("message", changePlay);
-			const fingeringContainer = document.getElementById("fingeringContainer");
-			fingeringContainer?.addEventListener("wheel", handleWheel);
-		});
-
-		onUnmounted(() => {
-			window.removeEventListener("message", changePlay);
-			const fingeringContainer = document.getElementById("fingeringContainer");
-			fingeringContainer?.removeEventListener("wheel", handleWheel);
-		});
-
-		const containerBox = computed(() => {
-			if (state.platform === IPlatform.PC || query.modelType) {
-				return {
-					paddingTop: "1rem",
-					paddingBottom: "",
-				};
-			}
-			if (data.subject === "hulusi-flute") {
-				return {
-					paddingTop: "3.1rem",
-					paddingBottom: ".8rem",
-				};
-			} else if (data.subject === "piccolo") {
-				return {
-					paddingTop: "4rem",
-					paddingBottom: ".8rem",
-				};
-			} else if (data.subject === "pan-flute") {
-				return {
-					paddingTop: "0",
-					paddingBottom: "0",
-				};
-			} else if (data.subject === "ocarina") {
-				return {
-					paddingTop: "1.2rem",
-					paddingBottom: "0",
-				};
-			} else if (data.subject === "melodica") {
-				return {
-					paddingTop: "2.8rem",
-					paddingBottom: "1.8rem",
-				};
-			} else {
-				return {
-					paddingTop: "",
-					paddingBottom: "",
-				};
-			}
-		});
-		return () => {
-			const relationship = fingerData.subject?.relationship?.[data.realKey] || [];
-			const rs: number[] = Array.isArray(relationship[1])
-				? relationship[fingerData.relationshipIndex]
-				: relationship;
-			const canTizhi = Array.isArray(relationship[1]);
-			return (
-				<div
-					class={[
-						styles.fingerBox,
-						state.platform !== IPlatform.PC &&
-						!query.modelType &&
-						fingerData.fingeringInfo.orientation === 1
-							? styles.fingerBottom
-							: styles.fingerRight,
-					]}
-				>
-					<div
-						class={styles.head}
-						style={{
-							paddingTop: data.paddingTop ? data.paddingTop : "",
-							paddingLeft: data.paddingLeft ? data.paddingLeft : "",
-						}}
-					>
-						<div class={styles.left}>
-							<button class={[styles.backBtn]} onClick={() => handleBack()}>
-								<img src={icons.icon_back} />
-							</button>
-							{data.subject !== "melodica" && (
-								<div
-									class={styles.baseBtn}
-									onClick={() => {
-										data.viewIndex++;
-										if (data.viewIndex > data.viewTotal) {
-											if (["pan-flute", "ocarina"].includes(data.subject)) {
-												data.viewIndex = 1;
-											} else {
-												data.viewIndex = 0;
-											}
-										}
-										getFingeringData();
-									}}
-								>
-									<img src={icons.icon_toggle} />
-									<span>切换视图</span>
-								</div>
-							)}
-						</div>
-						<div class={styles.rightBtn}>
-							<div class={styles.baseBtn} onClick={() => resetElement()}>
-								<img src={icons.icon_2_0} />
-								<span>还原</span>
-							</div>
-							<div
-								class={styles.baseBtn}
-								onClick={() => {
-									resetElement();
-									data.tipShow = !data.tipShow;
-								}}
-							>
-								<img src={icons.icon_2_1} />
-								<span>使用说明</span>
-							</div>
-						</div>
-					</div>
-					<div class={styles.fingerContent}>
-						<div class={styles.wrapFinger}>
-							<div
-								id="fingeringContainer"
-								class={styles.boxFinger}
-								style={{
-									paddingTop: containerBox.value.paddingTop,
-									paddingBottom: containerBox.value.paddingBottom,
-								}}
-							>
-								<div
-									style={{
-										transform: `translate3d(${data.transform.x}px,${data.transform.y}px,0px) scale(${data.transform.scale})`,
-										transition: data.transform.transition,
-									}}
-									class={[styles.fingeringContainer]}
-								>
-									<div class={styles.imgs}>
-										<img src={fingerData.subject?.json?.full} />
-										{rs.map((key: number | string, index: number) => {
-											const nk: string =
-												typeof key === "string" ? key.replace("active-", "") : String(key);
-											return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
-										})}
-										<div
-											style={{ left: data.viewIndex == 2 ? "0" : "64%" }}
-											class={[styles.tizhi, canTizhi && styles.canDisplay]}
-											onClick={() =>
-												(fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
-											}
-										>
-											替指
-										</div>
-										<div
-											id="finger-note-2"
-											style={{ left: "50%", transform: "translateX(-50%)" }}
-											class={styles.tizhi}
-											onClick={() =>
-												(fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)
-											}
-										></div>
-									</div>
-								</div>
-							</div>
-							<div
-								class={styles.notes}
-								style={{
-									paddingLeft: data.paddingLeft ? data.paddingLeft : "",
-								}}
-							>
-								<Button class={styles.noteBtn} onClick={() => scrollNoteBox("left")}>
-									<Icon name="arrow-left" />
-								</Button>
-								<div
-									class={[
-										styles.noteContent,
-										browsInfo.ios ? "" : styles.noteContentWrap,
-										data.huaweiPad && styles.huaweiPad,
-									]}
-								>
-									<div ref={noteBoxRef} class={styles.noteBox}>
-										{data.notes.map((note: IFIGNER_INSTRUMENT_Note, index: number) => {
-											const steps = new Array(Math.abs(note.step)).fill(1);
-											return (
-												<div
-													id={index == 0 ? "finger-note-0" : ""}
-													draggable={false}
-													class={styles.note}
-													onClick={() => noteClick(note)}
-												>
-													{data.realKey === note.realKey ? (
-														<img draggable={false} src={icons.icon_btn_ylow} />
-													) : (
-														<img draggable={false} src={icons.icon_btn_blue} />
-													)}
-
-													<div
-														class={[styles.noteKey, data.realKey === note.realKey && styles.keyActive]}
-													>
-														{note.step > 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
-
-														<div class={styles.noteName}>
-															<sup>{note.mark && (note.mark === "rise" ? "#" : "b")}</sup>
-															{note.key}
-														</div>
-														{note.step < 0 ? steps.map((n) => <span class={styles.dot}></span>) : null}
-													</div>
-												</div>
-											);
-										})}
-									</div>
-								</div>
-								<Button class={styles.noteBtn} onClick={() => scrollNoteBox("right")}>
-									<Icon name="arrow" />
-								</Button>
-							</div>
-						</div>
-						<div class={[styles.tips, data.tipShow ? "" : styles.tipHidden]}>
-							<div class={styles.tipTitle}>
-								<div class={styles.tipTitleName}>{fingerData.fingeringInfo.code}使用说明</div>
-								<Button class={styles.tipClose} onClick={() => (data.tipShow = false)}>
-									<Icon name="cross" size={19} color="#fff" />
-								</Button>
-							</div>
-							<div class={styles.iconBook}></div>
-							<div class={styles.tipContentbox}>
-								<div class={styles.tipContent}>
-									{data.tips.map((tip, tipIndex) => (
-										<div class={styles.tipItem}>
-											<div class={styles.iconWrap}>
-												<div class={styles.tipItemIcon}>{tipIndex + 1}</div>
-											</div>
-											<div>
-												{tip.name}: {tip.realName}
-											</div>
-										</div>
-									))}
-								</div>
-							</div>
-						</div>
-						{data.loadingSoundFonts && (
-							<div class={styles.loading}>
-								<div class={styles.loadingWrap}>
-									<img class={styles.loadingIcon} src={icon_loading_img} />
-									<Progress percentage={data.loadingSoundProgress} />
-									<div class={styles.loadingTip}>加载中,请稍后…</div>
-								</div>
-							</div>
-						)}
-					</div>
-					{!!data.tones.length && (
-						<>
-							{fingerData.fingeringInfo.name == "hulusi-flute" ? (
-								<div
-									id="finger-note-1"
-									class={[styles.toggleBtn, styles.toggleBtnhulusi]}
-									onClick={() => (data.tnoteShow = true)}
-								>
-									<div>
-										全按作
-										<div class={[styles.noteKey]}>
-											{data.activeTone.step > 0 ? <span class={styles.dot}></span> : null}
-
-											<div class={styles.noteName}>
-												<sup>
-													{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}
-												</sup>
-												{data.activeTone.key}
-											</div>
-											{data.activeTone.step < 0 ? <span class={styles.dot}></span> : null}
-										</div>
-									</div>
-									<img src={icons.icon_arrow} />
-								</div>
-							) : (
-								<div id="finger-note-1" class={styles.toggleBtn} onClick={() => (data.tnoteShow = true)}>
-									<div style={{ marginTop: "-4px" }}>
-										<sup>{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}</sup>
-										{data.activeTone.name}
-									</div>
-									调
-									<img src={icons.icon_arrow} />
-								</div>
-							)}
-						</>
-					)}
-
-					<Popup
-						class="tonePopup"
-						v-model:show={data.tnoteShow}
-						position={
-							state.platform !== IPlatform.PC &&
-							!query.modelType &&
-							fingerData.fingeringInfo.orientation === 1
-								? "bottom"
-								: "right"
-						}
-					>
-						<div class={styles.tones}>
-							<div class={styles.toneTitle}>
-								<div class={styles.tipTitleName}>移调</div>
-								<Button class={styles.tipClose} onClick={() => (data.tnoteShow = false)}>
-									<Icon name="cross" size={19} color="#fff" />
-								</Button>
-							</div>
-							<div class={styles.tipContentbox}>
-								<div class={styles.tipContent}>
-									<div class={styles.tipWrap}>
-										<Space size={0} class={styles.toneContent}>
-											{data.tones.map((tone: IFIGNER_INSTRUMENT_Note) => {
-												const steps = new Array(Math.abs(tone.step)).fill(1);
-												return (
-													<Button
-														class={[fingerData.fingeringInfo.name == "hulusi-flute" && styles.hulusiBtn]}
-														round
-														plain
-														type={
-															data.popupActiveTone.realName === tone.realName ? "primary" : "default"
-														}
-														onClick={() => {
-															data.popupActiveTone = tone;
-															setNotes();
-														}}
-													>
-														{fingerData.fingeringInfo.name == "hulusi-flute" ? (
-															<div style={{ display: "flex", alignItems: "center" }}>
-																全按作
-																<div class={[styles.noteKey, styles.hulusiNoteKey]}>
-																	{tone.step > 0 ? <span class={styles.dot}></span> : null}
-																	<div class={styles.noteName} style={{ fontSize: "0.25rem" }}>
-																		<sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
-																		{tone.key}
-																	</div>
-																	{tone.step < 0 ? <span class={styles.dot}></span> : null}
-																</div>
-															</div>
-														) : (
-															<div class={styles.noteName}>
-																<sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
-																{tone.name}
-															</div>
-														)}
-													</Button>
-												);
-											})}
-										</Space>
-									</div>
-									<div class={styles.toneAction}>
-										<img onClick={() => (data.tnoteShow = false)} src={icons.icon_action_cancel} />
-										<img
-											onClick={() => {
-												data.activeTone = data.popupActiveTone;
-												setNotes();
-												data.tnoteShow = false;
-											}}
-											src={icons.icon_action_confirm}
-										/>
-									</div>
-								</div>
-							</div>
-						</div>
-					</Popup>
-
-					{props.show && !data.loading && !data.loadingSoundFonts && (
-						<GuideIndex showGuide={false} list={["finger"]} />
-					)}
-				</div>
-			);
-		};
-	},
+  name: "viewFigner",
+  emits: ["close"],
+  props: {
+    show: {
+      type: Boolean,
+      default: true,
+    },
+    isComponent: {
+      type: Boolean,
+      default: false,
+    },
+    subject: {
+      type: String as PropType<IVocals>,
+      default: "",
+    },
+  },
+  setup(props, { emit }) {
+    const query = getQuery();
+    const browsInfo = browser();
+    const code = mappingVoicePart(query.code, "INSTRUMENT");
+    const subject = props.isComponent ? props.subject || "pan-flute" : code || "pan-flute";
+    const data = reactive({
+      loading: true,
+      subject: subject as any,
+      realKey: 0,
+      notes: [] as IFIGNER_INSTRUMENT_Note[],
+      tones: [] as IFIGNER_INSTRUMENT_Note[],
+      activeTone: {} as IFIGNER_INSTRUMENT_Note,
+      popupActiveTone: {} as IFIGNER_INSTRUMENT_Note,
+      activeToneName: "",
+      soundFonts: {} as any,
+      viewIndex: 0,
+      viewTotal: 1,
+      noteAudio: null as unknown as Howl,
+      transform: {
+        scale: 1,
+        x: 0,
+        y: 0,
+        startScale: 1,
+        startX: 0,
+        startY: 0,
+        transition: "",
+      },
+      tipShow: false,
+      tips: [] as IFIGNER_INSTRUMENT_Note[],
+
+      tnoteShow: false,
+      loadingSoundFonts: true,
+      loadingSoundProgress: 0,
+
+      huaweiPad: navigator?.userAgent?.includes("UAWEIVRD-W09") ? true : false,
+      paddingTop: "",
+      paddingLeft: "",
+      subjects: [] as any,
+      fingeringModeList: [
+        {
+          text: "指法模式",
+          value: "fingeringMode",
+          icon: icons.icon_click,
+        },
+        {
+          text: "听音模式",
+          value: "listenMode",
+          icon: icons.icon_listen,
+        },
+        {
+          text: "音阶模式",
+          value: "scaleMode",
+          icon: icons.icon_mode,
+        },
+      ],
+      fingeringMode: query.type || ("scaleMode" as "fingeringMode" | "listenMode" | "scaleMode"), // 模式
+      noteType: "all" as "#c" | "all", // 音调
+    });
+    const fingerData = reactive({
+      relationshipIndex: 0,
+      subject: null as unknown as ITypeFingering,
+      fingeringInfo: subjectFingering(data.subject),
+    });
+    if (!props.isComponent) {
+      state.fingeringInfo = fingerData.fingeringInfo;
+    }
+
+    const getAPPData = async (type: "top" | "left") => {
+      const screenData = await isSpecialShapedScreen();
+      if (screenData?.content) {
+        // console.log("🚀 ~ screenData:", screenData.content);
+        const { isSpecialShapedScreen, notchHeight } = screenData.content;
+        if (isSpecialShapedScreen) {
+          if (type === "top") {
+            data.paddingTop = 25 + "px";
+          }
+          if (type === "left") {
+            data.paddingLeft = 25 + "px";
+          }
+        }
+      }
+    };
+
+    const getHeadTop = () => {
+      if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 1) {
+        getAPPData("top");
+      }
+      if (!browsInfo.ios && fingerData.fingeringInfo.orientation === 0) {
+        getAPPData("left");
+      }
+    };
+
+    const getNotes = () => {
+      const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
+      if (fignerData) {
+        data.tones = fignerData.tones || [];
+        if (data.tones.length) {
+          data.activeTone = data.tones[0];
+          data.popupActiveTone = data.tones[0];
+        }
+        data.tips = fignerData.tips || [];
+        setNotes();
+        setTimeout(() => {
+          data.loading = false;
+        }, 600);
+      }
+    };
+    const setNotes = () => {
+      const fignerData = FIGNER_INSTRUMENT_DATA[data.subject as keyof typeof FIGNER_INSTRUMENT_DATA];
+      if (fignerData) {
+        const tempNotes = fignerData[`list${data.activeTone.realName || ""}`];
+        const appendNote: any = [];
+        tempNotes.forEach((note: any) => {
+          note.steps = new Array(Math.abs(note.step)).fill(1);
+          if (FIGNER_INSTRUMENT_REALKEY.includes(note.realKey)) {
+            appendNote.push(note);
+          }
+        });
+        // 判断是音符状态
+        data.notes = data.noteType === "#c" ? appendNote : tempNotes;
+        // data.notes = fignerData[`list${data.activeTone.realName || ""}`];
+      }
+    };
+    const getFingeringData = async () => {
+      const subject: any = data.subject + (data.viewIndex === 0 ? "" : data.viewIndex);
+      console.log("🚀 ~ subject:", subject);
+      fingerData.subject = await getFingeringConfig(subject);
+    };
+    const createAudio = (url: string) => {
+      return new Promise((resolve, reject) => {
+        const noteAudio = new Howl({
+          src: url,
+          loop: true,
+          onload: () => {
+            resolve(noteAudio);
+          },
+          onloaderror: () => {
+            reject(new Error(`加载音频失败`));
+          },
+        });
+      });
+    };
+    const getSounFonts = async () => {
+      const pathname = /(192|localhost)/.test(location.origin) ? "/" : location.pathname;
+      data.loadingSoundFonts = true;
+      try {
+        data.loadingSoundProgress = 0;
+        for (let i = 0; i < data.notes.length; i++) {
+          const note = data.notes[i];
+          // console.log("🚀 ~ note:", i);
+          let url = `${pathname}soundfonts/${data.subject}/`;
+          url += note.realName;
+          url += ".mp3";
+          data.soundFonts[note.realKey] = await createAudio(url);
+          data.loadingSoundProgress = Math.floor(((i + 1) / data.notes.length) * 100);
+        }
+        data.loadingSoundProgress = 100;
+      } catch (e: any) {
+        //
+        showToast(e.message);
+      }
+      api_cloudLoading();
+      data.loadingSoundFonts = false;
+    };
+
+    const selectSubjectType = (subject: string) => {
+      data.subjects.forEach((item: any) => {
+        if (item.value === subject) {
+          item.className = styles.selected;
+        } else {
+          item.className = "";
+        }
+      });
+    };
+
+    // 切换当前模式
+    const onChangeFingeringModel = () => {
+      //
+      if (playAction.listenLock) return;
+      if (playAction.showAnswerLoading) return;
+      if (data.fingeringMode === "scaleMode") {
+        if (["pan-flute", "ocarina"].includes(data.subject)) {
+          data.viewIndex = 1;
+        } else {
+          data.viewIndex = 0;
+        }
+        const o: any = {
+          "pan-flute": 2,
+          ocarina: 2,
+          piccolo: 2,
+          "hulusi-flute": 2,
+        };
+        data.viewTotal = o[data.subject] || 1;
+        data.fingeringMode = "listenMode";
+      } else if (data.fingeringMode === "listenMode") {
+        data.fingeringMode = "fingeringMode";
+      } else if (data.fingeringMode === "fingeringMode") {
+        data.fingeringMode = "scaleMode";
+        data.noteType = "all";
+      }
+      resetMode(true, 0);
+      __init();
+    };
+
+    const __init = () => {
+      getNotes();
+
+      selectSubjectType(data.subject);
+
+      if (data.fingeringMode === "fingeringMode") {
+        console.log(data.subject, "data.subject");
+        if (data.subject === "pan-flute") {
+          data.viewIndex = 3;
+        } else if (["pan-flute", "ocarina", "melodica"].includes(data.subject)) {
+          data.viewIndex = 1;
+        }
+      } else {
+        if (["pan-flute", "ocarina"].includes(data.subject)) {
+          data.viewIndex = 1;
+        }
+      }
+
+      const o: any = {
+        "pan-flute": 2,
+        ocarina: 2,
+        piccolo: 2,
+        "hulusi-flute": 2,
+      };
+      data.viewTotal = o[data.subject] || 1;
+      getFingeringData();
+      getSounFonts();
+      getHeadTop();
+    };
+
+    // 获取声部
+    const getSubjects = async () => {
+      try {
+        const subjects = await getSubjectList({
+          enableFlag: true,
+          delFlag: 0,
+          page: 1,
+          rows: 999,
+        });
+        const rows = subjects.data.rows || [];
+        rows.forEach((row: any) => {
+          data.subjects.push({
+            text: row.name,
+            value: mappingVoicePart(row.code, "INSTRUMENT"),
+            className: "",
+          });
+        });
+        console.log(data.subjects, "subjects");
+      } catch {
+        //
+      }
+    };
+
+    onBeforeMount(async () => {
+      state.platform = query.platform?.toLocaleUpperCase() || "";
+
+      await getSubjects();
+
+      __init();
+    });
+
+    /**
+     * 播放音频
+     * @param item 音频节点
+     * @param showNote 是否显示对应的指法
+     * @returns
+     */
+    const noteClick = (item: IFIGNER_INSTRUMENT_Note, showNote = true) => {
+      // console.log('音高', item.realKey)
+      if (data.noteAudio) {
+        data.noteAudio.stop();
+        if (data.realKey === item.realKey) {
+          data.realKey = 0;
+          data.noteAudio = null as unknown as Howl;
+          return;
+        }
+      }
+      if (showNote) {
+        data.realKey = item.realKey;
+      }
+      data.noteAudio = data.soundFonts[item.realKey];
+      data.noteAudio.play();
+    };
+    const handleStop = () => {
+      if (data.noteAudio) {
+        data.noteAudio.stop();
+        data.realKey = 0;
+        data.noteAudio = null as unknown as Howl;
+      }
+    };
+
+    /** 返回 */
+    const handleBack = () => {
+      // platform: query.platform,
+      handleStop();
+      if (props.isComponent) {
+        console.log("关闭");
+        emit("close");
+        return;
+      } else if (state.platform === IPlatform.PC) {
+        // 老师端,首页
+        window.parent.postMessage(
+          {
+            api: "iframe_exit",
+          },
+          "*"
+        );
+        return;
+        // if (fingerData.fingeringInfo.orientation === 0) {
+        // 	api_setRequestedOrientation(1);
+        // }
+      }
+      // 不在APP中,
+      if (!storeData.isApp) {
+        window.close();
+        return;
+      }
+      api_back();
+    };
+
+    onMounted(() => {
+      loadElement();
+      api_setStatusBarVisibility();
+    });
+    const loadElement = () => {
+      const fingeringContainer = document.getElementById("fingeringContainer");
+      // console.log("🚀 ~ fingeringContainer:", fingeringContainer);
+      const mc = new Hammer.Manager(fingeringContainer as HTMLElement);
+      mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
+      mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get("pan")]);
+      // mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
+      // mc.get("pinch").set({ enable: true });
+      mc.on("panstart pinchstart", function (ev) {
+        data.transform.transition = "";
+      });
+      mc.on("panmove pinchmove", function (ev) {
+        if (ev.type === "pinchmove") {
+          // console.log("🚀 ~ ev:", ev.type, ev.scale, ev.deltaX, ev.deltaY);
+          data.transform.scale = ev.scale * data.transform.startScale;
+          data.transform.x = data.transform.startX + ev.deltaX;
+          data.transform.y = data.transform.startY + ev.deltaY;
+        }
+        if (ev.type === "panmove") {
+          // console.log("🚀 ~ ev:", ev.type, ev.deltaX, ev.deltaY);
+          data.transform.x = data.transform.startX + ev.deltaX;
+          data.transform.y = data.transform.startY + ev.deltaY;
+        }
+      });
+      //
+      mc.on("hammer.input", function (ev) {
+        // console.log("🚀 ~ ev:", ev.type, ev.isFinal);
+        if (ev.isFinal) {
+          data.transform.startScale = data.transform.scale;
+          data.transform.startX = data.transform.x;
+          data.transform.startY = data.transform.y;
+        }
+      });
+    };
+    const resetElement = () => {
+      data.transform.transition = "all 0.3s";
+      nextTick(() => {
+        data.transform.scale = 1;
+        data.transform.x = 0;
+        data.transform.y = 0;
+        data.transform.startScale = 1;
+        data.transform.startX = 0;
+        data.transform.startY = 0;
+      });
+    };
+
+    const pageVisible = usePageVisibility();
+    watch(
+      () => pageVisible.value,
+      (val) => {
+        if (val === "hidden") {
+          console.log("页面隐藏停止播放");
+          handleStop();
+        }
+      }
+    );
+    /** 课件播放 */
+    const changePlay = (res: any) => {
+      if (res?.data?.api === "setPlayState") {
+        handleStop();
+      }
+    };
+
+    const noteBoxRef = ref();
+    const scrollNoteBox = (type: "left" | "right") => {
+      const width = noteBoxRef.value.offsetWidth / 2;
+      (noteBoxRef.value as unknown as HTMLElement).scrollBy({
+        left: type === "left" ? -width : width,
+        behavior: "smooth",
+      });
+    };
+
+    const playStatus = reactive({
+      gamut: false, // 是否播放音阶
+      gamutTimer: null as any, // 播放音阶定时器
+      answer: false, // 是否显示答案
+      action: false, // 是否开始播放
+    });
+
+    /** 音符切换 */
+    const noteChangeShow = () => {
+      // 播放音阶时不能切换
+      if (playStatus.gamut) return;
+      // 开始答题不能切换
+      if (playStatus.action) return;
+      if (data.noteType === "all") {
+        data.noteType = "#c";
+      } else {
+        data.noteType = "all";
+      }
+      getNotes();
+    };
+
+    // 开始播放音阶
+    const onGamutPlayOrPause = async () => {
+      if (playStatus.gamut) {
+        playStatus.gamut = false;
+        gaumntPause();
+      } else {
+        // 不管当前显示在哪个音老师滚动到开始位置
+        (noteBoxRef.value as unknown as HTMLElement).scroll({
+          left: 0,
+          top: 0,
+          behavior: "smooth",
+        });
+        playStatus.gamut = true;
+        const notes = data.notes;
+        let scrollCount = 0;
+        for (let i = 0; i < notes.length; i++) {
+          if (!playStatus.gamut) return false;
+          const activeDom = document.querySelectorAll(".note-class")[i] as any;
+          if (activeDom.offsetLeft >= noteBoxRef.value.offsetWidth + (noteBoxRef.value.offsetWidth / 2) * scrollCount - activeDom.offsetWidth) {
+            scrollNoteBox("right");
+            scrollCount++;
+          }
+          await gaumtPlay(notes[i]);
+        }
+
+        // // 处理播放到最后一个
+        setTimeout(() => {
+          playStatus.gamut = false;
+          gaumntPause();
+        }, 667);
+      }
+    };
+    const gaumtPlay = (note: any, status?: boolean) => {
+      return new Promise((resolve) => {
+        playStatus.gamutTimer = setTimeout(() => {
+          if (playStatus.gamut || status) {
+            noteClick(note);
+          }
+          resolve(note);
+        }, 667);
+      });
+    };
+    const gaumntPause = () => {
+      clearTimeout(playStatus.gamutTimer);
+      if (data.noteAudio) {
+        data.noteAudio.stop();
+        data.realKey = 0;
+        data.noteAudio = null as unknown as Howl;
+      }
+    };
+
+    /** 开始播放 */
+    const playAction = reactive({
+      exampleAnser: {} as any, // 示例声音
+      standardAnswer: {} as any, // 标准答案key
+      showAnswerLoading: false, // 显示按答案中
+      listenModeStatus: false, // 是否开始了模式
+      listenLock: false,
+      listenTipsStatus: false, // 开始播放状态
+      /** 0: 未答,1: 答对,2: 答错 */
+      userAnswerStatus: 0 as 0 | 1 | 2, // 用户回答状态
+      userAnswer: {} as any, // 用户答的数据
+    });
+    const onActionPlay = async () => {
+      if (playAction.listenLock) return;
+      if (playAction.showAnswerLoading) return;
+      playStatus.action = true;
+      playStatus.answer = true;
+      // 先暂停播放声音
+      gaumntPause();
+      if (data.fingeringMode === "fingeringMode") {
+        onFingeringMode();
+      } else if (data.fingeringMode === "listenMode") {
+        if (playAction.listenModeStatus) {
+          playAction.listenLock = true;
+          await fingeringPlay(playAction.standardAnswer, 1500, false);
+          gaumntPause();
+          playAction.listenLock = false;
+        } else {
+          onListenMode();
+        }
+      }
+    };
+
+    // 指法模式
+    const fingeringPlay = (note: any, timer = 1500, showNote = true) => {
+      return new Promise((resolve) => {
+        noteClick(note, showNote);
+        setTimeout(() => {
+          resolve(note);
+        }, timer);
+      });
+    };
+    const onFingeringMode = () => {
+      const randomIndex = Math.floor(Math.random() * data.notes.length);
+      playAction.standardAnswer = data.notes[randomIndex];
+      data.realKey = data.notes[randomIndex].realKey;
+      if (playAction.listenModeStatus) {
+        return;
+      }
+      playAction.listenModeStatus = true; // 是否开始听音
+      playAction.listenLock = true; // 锁
+      playAction.listenTipsStatus = true;
+      setTimeout(() => {
+        playAction.listenTipsStatus = false;
+        playAction.listenLock = false; // 锁
+      }, 2000);
+    };
+    // 听音模式
+    const onListenMode = async () => {
+      playAction.listenModeStatus = true; // 是否开始听音
+      playAction.listenLock = true; // 锁
+      playAction.listenTipsStatus = true;
+      // 设置并保存示例数据
+      let randomIndex = Math.floor(Math.random() * data.notes.length);
+      playAction.exampleAnser = data.notes[randomIndex];
+      data.realKey = playAction.exampleAnser.realKey;
+      scrollAnswer(playAction.exampleAnser.realKey);
+      await fingeringPlay(playAction.exampleAnser);
+      data.realKey = 0;
+      playAction.exampleAnser = {};
+      gaumntPause();
+      setTimeout(async () => {
+        // 设置答题数据
+        randomIndex = Math.floor(Math.random() * data.notes.length);
+        playAction.standardAnswer = data.notes[randomIndex];
+        await fingeringPlay(data.notes[randomIndex], 1500, false);
+        gaumntPause();
+        playAction.listenLock = false;
+        playAction.listenTipsStatus = false;
+      }, 1000);
+    };
+
+    // 显示答案
+    const onShowAnswer = async () => {
+      if (playAction.listenLock) return;
+      playAction.showAnswerLoading = true;
+      scrollAnswer(playAction.standardAnswer.realKey);
+      await fingeringPlay(playAction.standardAnswer);
+      resetMode(true, 0);
+      // }
+    };
+    // 滚动到对应答案位置
+    const scrollAnswer = (realKey?: any) => {
+      const tempRealKey = realKey || data.realKey;
+      const index = data.notes.findIndex((item: any) => item.realKey === tempRealKey);
+      const activeDom = document.querySelectorAll(".note-class")[index] as any;
+      if (activeDom) {
+        const aWidth = activeDom.offsetWidth;
+        const width = noteBoxRef.value.offsetWidth;
+        const aLeft = Math.max(activeDom?.offsetLeft - aWidth, 0);
+        (noteBoxRef.value as unknown as HTMLElement).scroll({
+          left: Math.max(aLeft - width / 2, 0),
+          top: 0,
+          behavior: "smooth",
+        });
+      }
+    };
+
+    /**
+     * 重置播放状态
+     * @param status 是否全部重置
+     * @param timer 延时时长(默认2s)
+     */
+    const resetMode = (status = true, timer = 2000) => {
+      // 2秒钟后重置
+      setTimeout(() => {
+        gaumntPause();
+        if (status) {
+          playAction.standardAnswer = {};
+          playAction.showAnswerLoading = false;
+          playAction.userAnswerStatus = 0;
+          playAction.userAnswer = {};
+          playAction.listenModeStatus = false;
+          playStatus.action = false;
+          playStatus.answer = false;
+          playStatus.gamut = false;
+          data.realKey = 0;
+        } else {
+          playAction.userAnswerStatus = 0;
+          playAction.userAnswer = {};
+        }
+      }, timer);
+    };
+
+    /** 滚轮缩放 */
+    const handleWheel = (e: WheelEvent) => {
+      e.preventDefault();
+      if (e.deltaY > 0) {
+        data.transform.scale -= 0.1;
+        if (data.transform.scale <= 0.5) {
+          data.transform.scale = 0.5;
+        }
+      } else {
+        data.transform.scale += 0.1;
+        if (data.transform.scale >= 2) {
+          data.transform.scale = 2;
+        }
+      }
+    };
+
+    onMounted(() => {
+      window.addEventListener("message", changePlay);
+      const fingeringContainer = document.getElementById("fingeringContainer");
+      fingeringContainer?.addEventListener("wheel", handleWheel);
+    });
+
+    onUnmounted(() => {
+      window.removeEventListener("message", changePlay);
+      const fingeringContainer = document.getElementById("fingeringContainer");
+      fingeringContainer?.removeEventListener("wheel", handleWheel);
+    });
+
+    const containerBox = computed(() => {
+      console.log(state.platform, query.modelType, data.fingeringMode, "fingering");
+      if (state.platform === IPlatform.PC || query.modelType) {
+        return {
+          paddingTop: "1rem",
+          paddingBottom: "",
+        };
+      }
+      if (data.fingeringMode === "scaleMode") {
+        if (data.subject === "hulusi-flute") {
+          return {
+            paddingTop: "3.1rem",
+            paddingBottom: ".8rem",
+          };
+        } else if (data.subject === "piccolo") {
+          return {
+            paddingTop: "4rem",
+            paddingBottom: ".8rem",
+          };
+        } else if (data.subject === "pan-flute") {
+          return {
+            paddingTop: "0",
+            paddingBottom: "0",
+          };
+        } else if (data.subject === "ocarina") {
+          return {
+            paddingTop: "1.2rem",
+            paddingBottom: "0",
+          };
+        } else if (data.subject === "melodica") {
+          return {
+            paddingTop: "2.8rem",
+            paddingBottom: "1.8rem",
+          };
+        } else {
+          return {
+            paddingTop: "",
+            paddingBottom: "",
+          };
+        }
+      } else {
+        if (data.subject === "hulusi-flute") {
+          return {
+            paddingTop: "2.1rem",
+            paddingBottom: "0rem",
+          };
+        } else if (data.subject === "piccolo") {
+          return {
+            paddingTop: "3rem",
+            paddingBottom: ".5rem",
+          };
+        } else if (data.subject === "pan-flute") {
+          return {
+            paddingTop: "0",
+            paddingBottom: "0",
+          };
+        } else if (data.subject === "ocarina") {
+          return {
+            paddingTop: "1rem",
+            paddingBottom: "0",
+          };
+        } else if (data.subject === "melodica") {
+          return {
+            paddingTop: "2.8rem",
+            paddingBottom: "0.8rem",
+          };
+        } else {
+          return {
+            paddingTop: "",
+            paddingBottom: "",
+          };
+        }
+      }
+    });
+
+    const listenText = computed(() => {
+      if (data.fingeringMode === "fingeringMode") {
+        if (playStatus.action) {
+          return "换一换";
+        } else {
+          return "开始练习";
+        }
+      } else if (data.fingeringMode === "listenMode") {
+        if (playStatus.action) {
+          return "再听一遍";
+        } else {
+          return "开始听音";
+        }
+      }
+      return "开始听音";
+    });
+
+    const modeText = computed(() => {
+      let text = "";
+      let icon = icons.icon_mode;
+      data.fingeringModeList.forEach((item: any) => {
+        if (item.value === data.fingeringMode) {
+          text = item.text;
+          icon = item.icon;
+        }
+      });
+      return {
+        text,
+        icon,
+      };
+    });
+
+    // 屏幕方向 0 竖,1 横
+    const orientationDirection = computed(() => {
+      return ["hulusi-flute", "piccolo"].includes(data.subject) ? 1 : 0;
+    });
+
+    const resultImg = (note: any) => {
+      if (data.realKey === note.realKey && !playStatus.action) {
+        return {
+          icon: icons.icon_btn_ylow,
+          status: false,
+        };
+      } else if (playAction.exampleAnser.realKey === note.realKey) {
+        return {
+          icon: icons.icon_btn_ylow,
+          status: false,
+        };
+      } else if (playAction.standardAnswer.realKey === note.realKey) {
+        // 没有开始答题
+        if (!playStatus.action) {
+          return {
+            icon: icons.icon_btn_ylow,
+            status: false,
+          };
+        }
+        // 显示答案中
+        if (playAction.showAnswerLoading) {
+          return {
+            icon: icons.icon_btn_green,
+            status: true,
+          };
+        }
+        // 用户答对
+        if (playAction.userAnswerStatus === 1) {
+          return {
+            icon: icons.icon_btn_green,
+            status: true,
+          };
+        }
+      } else {
+        // 用户答错
+        if (playAction.userAnswerStatus === 2 && playAction.userAnswer.realKey === note.realKey) {
+          return {
+            icon: icons.icon_btn_red,
+            status: true,
+          };
+        }
+      }
+      return {
+        icon: icons.icon_btn_blue,
+        status: true,
+      };
+    };
+    return () => {
+      const relationship = fingerData.subject?.relationship?.[data.realKey] || [];
+      const rs: number[] = Array.isArray(relationship[1]) ? relationship[fingerData.relationshipIndex] : relationship;
+      const canTizhi = Array.isArray(relationship[1]);
+      return (
+        <div class={[styles.fingerBox, state.platform !== IPlatform.PC && !query.modelType && fingerData.fingeringInfo.orientation === 1 ? styles.fingerBottom : styles.fingerRight]}>
+          <div
+            class={styles.head}
+            style={{
+              paddingTop: data.paddingTop ? data.paddingTop : "",
+              paddingLeft: data.paddingLeft ? data.paddingLeft : "",
+            }}
+          >
+            <div class={styles.left}>
+              <button class={[styles.backBtn]} onClick={() => handleBack()}>
+                <img src={icons.icon_back} />
+              </button>
+              <Popover
+                placement="bottom"
+                class={styles.popoverContainer}
+                actions={data.subjects}
+                onSelect={(val: any) => {
+                  if (data.subject === val.value) return;
+                  data.subject = val.value;
+                  data.viewIndex = 0;
+
+                  fingerData.fingeringInfo = subjectFingering(data.subject);
+                  console.log(fingerData.fingeringInfo);
+                  resetElement();
+                  resetMode(true, 0);
+                  api_setRequestedOrientation(orientationDirection.value);
+                  // 设置屏幕方向
+                  setTimeout(() => {
+                    __init();
+                  }, 100);
+                }}
+              >
+                {{
+                  reference: () => (
+                    <div
+                      class={styles.baseBtn}
+                      onClick={() => {
+                        //
+                      }}
+                    >
+                      <img src={icons.icon_change_instrument} />
+                      <span>切换乐器</span>
+                    </div>
+                  ),
+                }}
+              </Popover>
+
+              {data.subject !== "melodica" && data.fingeringMode === "scaleMode" && (
+                <div
+                  class={styles.baseBtn}
+                  onClick={() => {
+                    data.viewIndex++;
+                    if (data.viewIndex > data.viewTotal) {
+                      if (["pan-flute", "ocarina"].includes(data.subject)) {
+                        data.viewIndex = 1;
+                      } else {
+                        data.viewIndex = 0;
+                      }
+                    }
+                    getFingeringData();
+                  }}
+                >
+                  <img src={icons.icon_toggle} />
+                  <span>切换视图</span>
+                </div>
+              )}
+            </div>
+            <div class={styles.rightBtn}>
+              <div class={styles.baseBtn} onClick={onChangeFingeringModel}>
+                <img src={modeText.value.icon} />
+                <span>{modeText.value.text}</span>
+              </div>
+
+              <div class={styles.baseBtn} onClick={() => resetElement()}>
+                <img src={icons.icon_2_0} />
+                <span>还原</span>
+              </div>
+              <div
+                class={styles.baseBtn}
+                onClick={() => {
+                  resetElement();
+                  data.tipShow = !data.tipShow;
+                }}
+              >
+                <img src={icons.icon_2_1} />
+                <span>使用说明</span>
+              </div>
+            </div>
+          </div>
+          <div class={styles.fingerContent}>
+            <div class={styles.wrapFinger}>
+              <div
+                id="fingeringContainer"
+                class={styles.boxFinger}
+                style={{
+                  paddingTop: containerBox.value.paddingTop,
+                  paddingBottom: containerBox.value.paddingBottom,
+                }}
+              >
+                <div
+                  style={{
+                    transform: `translate3d(${data.transform.x}px,${data.transform.y}px,0px) scale(${data.transform.scale})`,
+                    transition: data.transform.transition,
+                  }}
+                  class={[styles.fingeringContainer]}
+                >
+                  <div class={styles.imgs}>
+                    <img src={data.fingeringMode === "scaleMode" ? fingerData.subject?.json?.full : fingerData.subject?.json?.full1} />
+                    {rs.map((key: number | string, index: number) => {
+                      const nk: string = typeof key === "string" ? key.replace("active-", "") : String(key);
+                      return <img data-index={nk} src={fingerData.subject?.json?.[nk]} />;
+                    })}
+                    <div style={{ left: data.viewIndex == 2 ? "0" : "64%" }} class={[styles.tizhi, canTizhi && styles.canDisplay]} onClick={() => (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)}>
+                      替指
+                    </div>
+                    <div id="finger-note-2" style={{ left: "50%", transform: "translateX(-50%)" }} class={styles.tizhi} onClick={() => (fingerData.relationshipIndex = fingerData.relationshipIndex === 0 ? 1 : 0)}></div>
+                  </div>
+                </div>
+              </div>
+              <div
+                class={styles.notes}
+                style={{
+                  paddingLeft: data.paddingLeft ? data.paddingLeft : "",
+                }}
+              >
+                {playAction.listenTipsStatus && <div class={[styles.tipsT, data.fingeringMode === "fingeringMode" ? styles.playTips2 : styles.playTips]}></div>}
+                {playAction.userAnswerStatus === 1 && <div class={[styles.tipsT, styles.playSuccess]}></div>}
+                {playAction.userAnswerStatus === 2 && <div class={[styles.tipsT, styles.playError]}></div>}
+                <Button class={styles.noteBtn} onClick={() => scrollNoteBox("left")}>
+                  <Icon name="arrow-left" />
+                </Button>
+                <div class={[styles.noteContent, data.fingeringMode !== "scaleMode" && orientationDirection.value === 0 && styles.noteContentOther, browsInfo.ios ? "" : styles.noteContentWrap, data.huaweiPad && styles.huaweiPad]}>
+                  {/* 判断是否为音阶模式 */}
+                  {data.fingeringMode !== "scaleMode" && (
+                    <div draggable={false} class={styles.note} onClick={noteChangeShow}>
+                      <img draggable={false} src={data.noteType === "all" ? icons.icon_btn_orange : icons.icon_btn_orange2} />
+                    </div>
+                  )}
+
+                  {/* [styles.noteContent, browsInfo.ios ? "" : styles.noteContentWrap, data.huaweiPad && styles.huaweiPad] */}
+                  <div class={styles.lastNoteContent}>
+                    <div ref={noteBoxRef} class={styles.noteBox}>
+                      {data.notes.map((note: IFIGNER_INSTRUMENT_Note, index: number) => {
+                        const steps = new Array(Math.abs(note.step)).fill(1);
+                        return (
+                          <div
+                            id={index == 0 ? "finger-note-0" : ""}
+                            draggable={false}
+                            class={[styles.note, "note-class"]}
+                            key={note.realKey}
+                            onClick={async () => {
+                              // 判断是否在播放音阶
+                              if (playStatus.gamut) return;
+                              if (playAction.listenLock) return;
+                              if (playAction.showAnswerLoading) return;
+                              if (playStatus.action) {
+                                playAction.userAnswer = note;
+                                // 判断用户答题
+                                const userResult = note.realKey === playAction.standardAnswer.realKey ? 1 : 2;
+                                playAction.userAnswerStatus = userResult;
+                                playAction.listenLock = true;
+                                data.realKey = note.realKey;
+                                await fingeringPlay(note, 1000);
+                                resetMode(userResult === 1 ? true : false, 0);
+                                data.realKey = 0;
+
+                                // 如果是指法模式显示完之后要还原
+                                if (data.fingeringMode === "fingeringMode" && userResult === 2) {
+                                  // 延迟显示,因为重置的时候有一个异步操作
+                                  setTimeout(() => {
+                                    data.realKey = playAction.standardAnswer.realKey;
+                                  }, 10);
+                                }
+                                playAction.listenLock = false;
+                              } else {
+                                noteClick(note);
+                              }
+                            }}
+                          >
+                            <img draggable={false} src={resultImg(note).icon} />
+
+                            {playStatus.action && ((playAction.showAnswerLoading && playAction.standardAnswer.realKey === note.realKey) || (playAction.userAnswerStatus === 1 && playAction.userAnswer.realKey === note.realKey)) ? <span class={styles.showAnswer}></span> : ""}
+                            {playStatus.action && playAction.userAnswerStatus === 2 && playAction.userAnswer.realKey === note.realKey ? <span class={[styles.showAnswer, styles.errorAnswer]}></span> : ""}
+                            <div
+                              class={[
+                                styles.noteKey,
+                                ((data.realKey === note.realKey && !playStatus.action) ||
+                                  (playStatus.action && ((playAction.showAnswerLoading && playAction.standardAnswer.realKey === note.realKey) || (playAction.userAnswerStatus === 1 && playAction.userAnswer.realKey === note.realKey))) ||
+                                  (playStatus.action && playAction.userAnswerStatus === 2 && playAction.userAnswer.realKey === note.realKey)) &&
+                                  styles.keyActive,
+                              ]}
+                            >
+                              {/* 显示对应的点 */}
+                              {note.step > 0 ? steps.map((n: any) => <span class={styles.dot}></span>) : null}
+
+                              <div class={styles.noteName}>
+                                <sup>{note.mark && (note.mark === "rise" ? "#" : "b")}</sup>
+                                {note.key}
+                              </div>
+                              {/* 显示对应的点 */}
+                              {note.step < 0 ? steps.map((n: any) => <span class={styles.dot}></span>) : null}
+                            </div>
+                          </div>
+                        );
+                      })}
+                    </div>
+                  </div>
+                </div>
+                <Button class={styles.noteBtn} onClick={() => scrollNoteBox("right")}>
+                  <Icon name="arrow" />
+                </Button>
+              </div>
+              {data.fingeringMode !== "scaleMode" && (
+                <div class={styles.optionBtns}>
+                  <Button class={[styles.oBtn, styles.gamut, playStatus.action && styles.disabled]} round onClick={onGamutPlayOrPause}>
+                    {playStatus.gamut ? "暂停" : "播放音阶"}
+                  </Button>
+                  <Button class={[styles.oBtn, styles.play, playStatus.gamut && styles.disabled]} round onClick={onActionPlay}>
+                    {listenText.value}
+                  </Button>
+                  <Button class={[styles.oBtn, styles.success, !playStatus.answer && styles.disabled]} round onClick={onShowAnswer}>
+                    显示答案
+                  </Button>
+                </div>
+              )}
+            </div>
+            <div class={[styles.tips, data.tipShow ? "" : styles.tipHidden]}>
+              <div class={styles.tipTitle}>
+                <div class={styles.tipTitleName}>{fingerData.fingeringInfo.code}使用说明</div>
+                <Button class={styles.tipClose} onClick={() => (data.tipShow = false)}>
+                  <Icon name="cross" size={19} color="#fff" />
+                </Button>
+              </div>
+              <div class={styles.iconBook}></div>
+              <div class={styles.tipContentbox}>
+                <div class={styles.tipContent}>
+                  {data.tips.map((tip, tipIndex) => (
+                    <div class={styles.tipItem}>
+                      <div class={styles.iconWrap}>
+                        <div class={styles.tipItemIcon}>{tipIndex + 1}</div>
+                      </div>
+                      <div>
+                        {tip.name}: {tip.realName}
+                      </div>
+                    </div>
+                  ))}
+                </div>
+              </div>
+            </div>
+            {data.loadingSoundFonts && (
+              <div class={styles.loading}>
+                <div class={styles.loadingWrap}>
+                  <img class={styles.loadingIcon} src={icon_loading_img} />
+                  <Progress percentage={data.loadingSoundProgress} />
+                  <div class={styles.loadingTip}>加载中,请稍后…</div>
+                </div>
+              </div>
+            )}
+          </div>
+          {!!data.tones.length && data.fingeringMode === "scaleMode" && (
+            <>
+              {fingerData.fingeringInfo.name == "hulusi-flute" ? (
+                <div id="finger-note-1" class={[styles.toggleBtn, styles.toggleBtnhulusi]} onClick={() => (data.tnoteShow = true)}>
+                  <div>
+                    全按作
+                    <div class={[styles.noteKey]}>
+                      {data.activeTone.step > 0 ? <span class={styles.dot}></span> : null}
+
+                      <div class={styles.noteName}>
+                        <sup>{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}</sup>
+                        {data.activeTone.key}
+                      </div>
+                      {data.activeTone.step < 0 ? <span class={styles.dot}></span> : null}
+                    </div>
+                  </div>
+                  <img src={icons.icon_arrow} />
+                </div>
+              ) : (
+                <div id="finger-note-1" class={styles.toggleBtn} onClick={() => (data.tnoteShow = true)}>
+                  <div style={{ marginTop: "-4px" }}>
+                    <sup>{data.activeTone.mark && (data.activeTone.mark === "rise" ? "#" : "b")}</sup>
+                    {data.activeTone.name}
+                  </div>
+                  调
+                  <img src={icons.icon_arrow} />
+                </div>
+              )}
+            </>
+          )}
+
+          <Popup class="tonePopup" v-model:show={data.tnoteShow} position={state.platform !== IPlatform.PC && !query.modelType && fingerData.fingeringInfo.orientation === 1 ? "bottom" : "right"}>
+            <div class={styles.tones}>
+              <div class={styles.toneTitle}>
+                <div class={styles.tipTitleName}>移调</div>
+                <Button class={styles.tipClose} onClick={() => (data.tnoteShow = false)}>
+                  <Icon name="cross" size={19} color="#fff" />
+                </Button>
+              </div>
+              <div class={styles.tipContentbox}>
+                <div class={styles.tipContent}>
+                  <div class={styles.tipWrap}>
+                    <Space size={0} class={styles.toneContent}>
+                      {data.tones.map((tone: IFIGNER_INSTRUMENT_Note) => {
+                        const steps = new Array(Math.abs(tone.step)).fill(1);
+                        return (
+                          <Button
+                            class={[fingerData.fingeringInfo.name == "hulusi-flute" && styles.hulusiBtn]}
+                            round
+                            plain
+                            type={data.popupActiveTone.realName === tone.realName ? "primary" : "default"}
+                            onClick={() => {
+                              data.popupActiveTone = tone;
+                              setNotes();
+                            }}
+                          >
+                            {fingerData.fingeringInfo.name == "hulusi-flute" ? (
+                              <div style={{ display: "flex", alignItems: "center" }}>
+                                全按作
+                                <div class={[styles.noteKey, styles.hulusiNoteKey]}>
+                                  {tone.step > 0 ? <span class={styles.dot}></span> : null}
+                                  <div class={styles.noteName} style={{ fontSize: "0.25rem" }}>
+                                    <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
+                                    {tone.key}
+                                  </div>
+                                  {tone.step < 0 ? <span class={styles.dot}></span> : null}
+                                </div>
+                              </div>
+                            ) : (
+                              <div class={styles.noteName}>
+                                <sup>{tone.mark && (tone.mark === "rise" ? "#" : "b")}</sup>
+                                {tone.name}
+                              </div>
+                            )}
+                          </Button>
+                        );
+                      })}
+                    </Space>
+                  </div>
+                  <div class={styles.toneAction}>
+                    <img onClick={() => (data.tnoteShow = false)} src={icons.icon_action_cancel} />
+                    <img
+                      onClick={() => {
+                        data.activeTone = data.popupActiveTone;
+                        setNotes();
+                        data.tnoteShow = false;
+                      }}
+                      src={icons.icon_action_confirm}
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </Popup>
+
+          {props.show && !data.loading && !data.loadingSoundFonts && <GuideIndex showGuide={false} list={["finger"]} />}
+        </div>
+      );
+    };
+  },
 });

+ 229 - 205
src/view/fingering/fingering-config.ts

@@ -28,30 +28,32 @@ type ITypeContent = {
 };
 
 export type IVocals =
-	| "flute"
-	| "clarinet"
-	| "saxophone"
-	| "trumpet"
-	| "horn"
-	| "trombone"
-	| "up-bass-horn"
-	| "small-drum"
-	| "tuba"
-	| "piccolo"
-	| "piccolo1"
-	| "piccolo2"
-	| "hulusi-flute"
-	| "hulusi-flute1"
-	| "hulusi-flute2"
-	| "pan-flute"
-	| "pan-flute1"
-	| "pan-flute2"
-	| "pan-flute3"
-	| "pan-flute4"
-	| "ocarina"
-	| "ocarina1"
-	| "ocarina2"
-	| "melodica";
+  | "flute"
+  | "clarinet"
+  | "saxophone"
+  | "trumpet"
+  | "horn"
+  | "trombone"
+  | "up-bass-horn"
+  | "small-drum"
+  | "tuba"
+  | "piccolo"
+  | "piccolo1"
+  | "piccolo2"
+  | "hulusi-flute"
+  | "hulusi-flute1"
+  | "hulusi-flute2"
+  | "pan-flute"
+  | "pan-flute1"
+  | "pan-flute2"
+  | "pan-flute3"
+  | "pan-flute4"
+  | "ocarina"
+  | "ocarina1"
+  | "ocarina2"
+  | "melodica"
+  | "melodica1"
+  | "baroque-recorder";
 
 /** 映射声部ID */
 export const mappingVoicePart = (
@@ -341,185 +343,207 @@ export const subjectFingering = (subjectId: number | string): IFingering => {
 };
 
 export const getFingeringConfig = async (type: IVocals | undefined): Promise<ITypeFingering> => {
-	switch (type) {
-		case "flute":
-			const flute = await import(`./fingering-img/flute/index.json`);
-			return {
-				json: flute.default,
-				relationship: relationships.flute,
-				height: "60px",
-				styles: {},
-			};
-		case "clarinet":
-			const clarinet = await import(`./fingering-img/clarinet/index.json`);
-			return {
-				json: clarinet.default,
-				relationship: relationships.clarinet,
-				styles: {
-					marginLeft: ".4rem",
-					marginRight: ".7rem",
-				},
-			};
-		case "trumpet":
-			const trumpet = await import(`./fingering-img/trumpet/index.json`);
-			return {
-				json: trumpet.default,
-				relationship: relationships.trumpet,
-				// maxWidth: 150,
-			};
-		case "horn":
-			const horn = await import(`./fingering-img/horn/index.json`);
-			return {
-				json: horn.default,
-				relationship: relationships.horn,
-				height: "212px",
-				width: "252px",
-			};
-		case "tuba":
-			const tuba = await import(`./fingering-img/tuba/index.json`);
-			return {
-				json: tuba.default,
-				relationship: relationships.tuba,
-			};
-		case "piccolo":
-			const piccolo = await import(`./fingering-img/piccolo/index.json`);
-			return {
-				json: piccolo.default,
-				relationship: relationships.piccolo,
-			};
-		case "piccolo1":
-			const piccolo1 = await import(`./fingering-img/piccolo1/index.json`);
-			return {
-				json: piccolo1.default,
-				relationship: relationships.piccolo,
-			};
-		case "piccolo2":
-			const piccolo2 = await import(`./fingering-img/piccolo2/index.json`);
-			return {
-				json: piccolo2.default,
-				relationship: relationships.piccolo,
-			};
-		case "up-bass-horn":
-			const upBassHorn = await import(`./fingering-img/up-bass-horn/index.json`);
-			return {
-				json: upBassHorn.default,
-				relationship: relationships["up-bass-horn"],
-			};
-		case "trombone":
-			const trombone = await import(`./fingering-img/trombone/index.json`);
-			return {
-				json: trombone.default,
-				relationship: relationships["trombone"],
-			};
-		case "saxophone":
-			const saxophone = await import(`./fingering-img/saxophone/index.json`);
-			return {
-				json: saxophone.default,
-				relationship: relationships["saxophone"],
-				styles: {
-					marginLeft: ".2rem",
-					marginRight: ".3rem",
-				},
-			};
-		case "small-drum":
-			const smallDrum = await import(`./fingering-img/small-drum/index.json`);
-			return {
-				json: smallDrum.default,
-				relationship: relationships["up-bass-horn"],
-				width: "180px",
-			};
-		case "hulusi-flute":
-			const hulusi = await import(`./fingering-img/hulusi-flute/index.json`);
-			return {
-				json: hulusi.default,
-				relationship: relationships.hulusi,
-				// width: '180px',
-				styles: {
-					marginLeft: ".6rem",
-					marginRight: ".7rem",
-				},
-			};
-		case "hulusi-flute1":
-			const hulusi1 = await import(`./fingering-img/hulusi-flute1/index.json`);
-			return {
-				json: hulusi1.default,
-				relationship: relationships.hulusi,
-				// width: '180px',
-				styles: {
-					marginLeft: ".6rem",
-					marginRight: ".7rem",
-				},
-			};
-		case "hulusi-flute2":
-			const hulusi2 = await import(`./fingering-img/hulusi-flute2/index.json`);
-			return {
-				json: hulusi2.default,
-				relationship: relationships.hulusi,
-				// width: '180px',
-				styles: {
-					marginLeft: ".6rem",
-					marginRight: ".7rem",
-				},
-			};
-		case "pan-flute":
-			const pan = await import(`./fingering-img/pan-flute/index.json`);
-			return {
-				json: pan.default,
-				relationship: relationships.pan,
-			};
-		case "pan-flute1":
-			const pan1 = await import(`./fingering-img/pan-flute1/index.json`);
-			return {
-				json: pan1.default,
-				relationship: relationships.pan,
-			};
-		case "pan-flute2":
-			const pan2 = await import(`./fingering-img/pan-flute2/index.json`);
-			return {
-				json: pan2.default,
-				relationship: relationships.pan,
-			};
-		case "ocarina":
-			const ocarina = await import(`./fingering-img/ocarina/index.json`);
-			return {
-				json: ocarina.default,
-				relationship: relationships.ocarina,
-				width: "180px",
-				styles: {
-					marginTop: "auto",
-				},
-			};
-		case "ocarina1":
-			const ocarina1 = await import(`./fingering-img/ocarina1/index.json`);
-			return {
-				json: ocarina1.default,
-				relationship: relationships.ocarina,
-				width: "180px",
-				styles: {
-					marginTop: "auto",
-				},
-			};
-		case "ocarina2":
-			const ocarina2 = await import(`./fingering-img/ocarina2/index.json`);
-			return {
-				json: ocarina2.default,
-				relationship: relationships.ocarina,
-				width: "180px",
-				styles: {
-					marginTop: "auto",
-				},
-			};
-		case "melodica":
-			const melodica = await import(`./fingering-img/melodica/index.json`);
-			return {
-				json: melodica.default,
-				relationship: relationships.melodica,
-				height: "80px",
-				styles: {
-					marginTop: "auto",
-				},
-			};
-		default:
-			return null;
-	}
+  switch (type) {
+    case "flute":
+      const flute = await import(`./fingering-img/flute/index.json`);
+      return {
+        json: flute.default,
+        relationship: relationships.flute,
+        height: "60px",
+        styles: {},
+      };
+    case "clarinet":
+      const clarinet = await import(`./fingering-img/clarinet/index.json`);
+      return {
+        json: clarinet.default,
+        relationship: relationships.clarinet,
+        styles: {
+          marginLeft: ".4rem",
+          marginRight: ".7rem",
+        },
+      };
+    case "trumpet":
+      const trumpet = await import(`./fingering-img/trumpet/index.json`);
+      return {
+        json: trumpet.default,
+        relationship: relationships.trumpet,
+        // maxWidth: 150,
+      };
+    case "horn":
+      const horn = await import(`./fingering-img/horn/index.json`);
+      return {
+        json: horn.default,
+        relationship: relationships.horn,
+        height: "212px",
+        width: "252px",
+      };
+    case "tuba":
+      const tuba = await import(`./fingering-img/tuba/index.json`);
+      return {
+        json: tuba.default,
+        relationship: relationships.tuba,
+      };
+    case "piccolo":
+      const piccolo = await import(`./fingering-img/piccolo/index.json`);
+      return {
+        json: piccolo.default,
+        relationship: relationships.piccolo,
+      };
+	case "baroque-recorder":
+	const baroqueRecorder = await import(`./fingering-img/baroque-recorder/index.json`);
+	return {
+		json: baroqueRecorder.default,
+		relationship: relationships.baroqueRecorder,
+	};	  
+    case "piccolo1":
+      const piccolo1 = await import(`./fingering-img/piccolo1/index.json`);
+      return {
+        json: piccolo1.default,
+        relationship: relationships.piccolo,
+      };
+    case "piccolo2":
+      const piccolo2 = await import(`./fingering-img/piccolo2/index.json`);
+      return {
+        json: piccolo2.default,
+        relationship: relationships.piccolo,
+      };
+    case "up-bass-horn":
+      const upBassHorn = await import(`./fingering-img/up-bass-horn/index.json`);
+      return {
+        json: upBassHorn.default,
+        relationship: relationships["up-bass-horn"],
+      };
+    case "trombone":
+      const trombone = await import(`./fingering-img/trombone/index.json`);
+      return {
+        json: trombone.default,
+        relationship: relationships["trombone"],
+      };
+    case "saxophone":
+      const saxophone = await import(`./fingering-img/saxophone/index.json`);
+      return {
+        json: saxophone.default,
+        relationship: relationships["saxophone"],
+        styles: {
+          marginLeft: ".2rem",
+          marginRight: ".3rem",
+        },
+      };
+    case "small-drum":
+      const smallDrum = await import(`./fingering-img/small-drum/index.json`);
+      return {
+        json: smallDrum.default,
+        relationship: relationships["up-bass-horn"],
+        width: "180px",
+      };
+    case "hulusi-flute":
+      const hulusi = await import(`./fingering-img/hulusi-flute/index.json`);
+      return {
+        json: hulusi.default,
+        relationship: relationships.hulusi,
+        // width: '180px',
+        styles: {
+          marginLeft: ".6rem",
+          marginRight: ".7rem",
+        },
+      };
+    case "hulusi-flute1":
+      const hulusi1 = await import(`./fingering-img/hulusi-flute1/index.json`);
+      return {
+        json: hulusi1.default,
+        relationship: relationships.hulusi,
+        // width: '180px',
+        styles: {
+          marginLeft: ".6rem",
+          marginRight: ".7rem",
+        },
+      };
+    case "hulusi-flute2":
+      const hulusi2 = await import(`./fingering-img/hulusi-flute2/index.json`);
+      return {
+        json: hulusi2.default,
+        relationship: relationships.hulusi,
+        // width: '180px',
+        styles: {
+          marginLeft: ".6rem",
+          marginRight: ".7rem",
+        },
+      };
+    case "pan-flute":
+      const pan = await import(`./fingering-img/pan-flute/index.json`);
+      return {
+        json: pan.default,
+        relationship: relationships.pan,
+      };
+    case "pan-flute1":
+      const pan1 = await import(`./fingering-img/pan-flute1/index.json`);
+      return {
+        json: pan1.default,
+        relationship: relationships.pan,
+      };
+    case "pan-flute2":
+      const pan2 = await import(`./fingering-img/pan-flute2/index.json`);
+      return {
+        json: pan2.default,
+        relationship: relationships.pan,
+      };
+    case "pan-flute3":
+      const pan3 = await import(`./fingering-img/pan-flute3/index.json`);
+      return {
+        json: pan3.default,
+        relationship: relationships.pan,
+      };
+    case "ocarina":
+      const ocarina = await import(`./fingering-img/ocarina/index.json`);
+      return {
+        json: ocarina.default,
+        relationship: relationships.ocarina,
+        width: "180px",
+        styles: {
+          marginTop: "auto",
+        },
+      };
+    case "ocarina1":
+      const ocarina1 = await import(`./fingering-img/ocarina1/index.json`);
+      return {
+        json: ocarina1.default,
+        relationship: relationships.ocarina,
+        width: "180px",
+        styles: {
+          marginTop: "auto",
+        },
+      };
+    case "ocarina2":
+      const ocarina2 = await import(`./fingering-img/ocarina2/index.json`);
+      return {
+        json: ocarina2.default,
+        relationship: relationships.ocarina,
+        width: "180px",
+        styles: {
+          marginTop: "auto",
+        },
+      };
+    case "melodica":
+      const melodica = await import(`./fingering-img/melodica/index.json`);
+      return {
+        json: melodica.default,
+        relationship: relationships.melodica,
+        height: "80px",
+        styles: {
+          marginTop: "auto",
+        },
+      };
+    case "melodica1":
+      const melodica1 = await import(`./fingering-img/melodica1/index.json`);
+      return {
+        json: melodica1.default,
+        relationship: relationships.melodica,
+        height: "80px",
+        styles: {
+          marginTop: "auto",
+        },
+      };
+    default:
+      return null;
+  }
 };

+ 43 - 0
src/view/fingering/fingering-relationships.ts

@@ -492,6 +492,49 @@ const relationships = {
 		83: [18],
 		84: [19],
 	},
+	baroqueRecorder: { // 英式竖笛
+		// 0: [1, 2, 3, 4, 5, 6, 7, 8, 11, 33, 77, 88],
+		// 888: [11, 2, 33, 4, 5, 6, 7, 8],
+		60: [11, 2, 33, 4, 5, 6, 7, 8],
+		61: [11, 2, 33, 4, 5, 6, 7, 88],
+		62: [11, 2, 33, 4, 5, 6, 7],
+		63: [11, 2, 33, 4, 5, 6, 77],
+		64: [11, 2, 33, 4, 5, 6],
+		65: [11, 2, 33, 4, 5, 7, 8],
+		66: [11, 2, 33, 4, 6, 7],
+		67: [11, 2, 33, 4],
+		68: [11, 2, 33, 5, 6, 77],
+		69: [11, 2 ,33],
+		70: [11, 2, 4, 5],
+		71: [11, 2],
+		72: [11, 33],
+		73: [2, 33],
+		74: [33],
+		75: [ 33, 4, 5, 6, 7],
+		76: [1, 2, 33, 4, 5, 6],
+		77: [1, 2, 33, 4, 5, 7],
+		78: [1, 2, 33, 4, 6],
+		79: [1, 2, 33, 4],
+		80: [1, 2, 33, 5],
+		81: [1, 2, 33],
+		82: [1, 2, 33, 5, 6, 7],
+		83: [1, 2, 33, 5, 6],
+		84: [1, 2, 5, 6],
+		85: [1, 2, 4, 5, 7, 8],
+		86: [1, 2, 4, 5, 7, 8],
+		87: [1, 2, 4],
+		88: [1, 2, 33, 4, 5, 6],
+		89: [1, 2, 33, 4, 5, 7],
+		90: [1, 2, 33, 4, 6],
+		91: [1, 2, 33, 4],
+		92: [1, 2, 33, 5],
+		93: [1, 2, 33],
+		94: [1, 2, 33, 5, 6, 7],
+		95: [1, 2, 33, 5, 6],
+		96: [1, 2, 5, 6],
+		97: [1, 2, 4, 5, 7, 8],
+		98: [1, 2, 4],
+	  },	
 };
 
 export default relationships;