소스 검색

feat: 云教练加载时长优化

TIANYONG 1 주 전
부모
커밋
0aa8beb93b

+ 2 - 0
src/helpers/calcSpeed.ts

@@ -123,7 +123,9 @@ export type GradualItem = {
  */
 export const getGradualLengthByXml = (xml: string) => {
 	const firstPartXml = onlyVisible(xml, 0, 'calc')
+	//console.time('解析xml 耗时2')
 	const xmlParse = new DOMParser().parseFromString(firstPartXml, "text/xml");
+	//console.timeEnd('解析xml 耗时2')
 	const measures = Array.from(xmlParse.querySelectorAll("measure"));
 	const notes = Array.from(xmlParse.querySelectorAll("note"));
 	const words = Array.from(xmlParse.querySelectorAll("words"));

+ 21 - 6
src/helpers/formateMusic.ts

@@ -15,6 +15,7 @@ import {
 } from "/osmd-extended/src";
 import { GradualChange, speedInfo } from "./calcSpeed";
 import { beatUnitTo, speedBeatTo } from "/src/helpers/beatConfig"
+import { xmlDocRef } from "/src/view/music-score"
 
 const browserInfo = browser();
 dayjs.extend(duration);
@@ -250,13 +251,16 @@ export type CustomInfo = {
 };
 
 /** 从xml中获取自定义信息,并删除多余的字符串 */
-export const getCustomInfo = (xml: string): CustomInfo => {
+export const getCustomInfo = (xml: string, resourceType?: string): CustomInfo => {
 	const data = {
 		showSpeed: true,
 		parsedXML: xml,
 	};
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-	const words: any = xmlParse.getElementsByTagName("words");
+	//console.time('解析xml 耗时3')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value && resourceType === 'init' ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时3')
+	const words: any = xmlParse?.getElementsByTagName("words");
 	for (const word of words) {
 		if (word && word.textContent?.trim() === "隐藏速度") {
 			data.showSpeed = false;
@@ -365,7 +369,10 @@ export const onlyVisible = (xml: string, partIndex: number, resourceType?: strin
 	if (!xml) return "";
 	// console.log('原始xml')
 	const detailId = state.cbsExamSongId + "";
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	//console.time('解析xml 耗时4')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value && !resourceType ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时4')
 	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
 	const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0]?.textContent?.trim() || "");
 	const parts: any = xmlParse.getElementsByTagName("part");
@@ -535,7 +542,9 @@ export const onlyVisible2 = (xml: string): string => {
 	if (!xml) return "";
 	// console.log('原始xml')
 	//const detailId = state.cbsExamSongId + "";
+	console.time('解析xml 耗时5')
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	console.timeEnd('解析xml 耗时5')
 	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
 	//const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0]?.textContent?.trim() || "");
 	//state.partListNames = partListNames;
@@ -628,7 +637,9 @@ export const formatZoom = (num = 1) => {
 /** 妙极客多分轨的曲子,可能没有part-name标签,需要手动加上该标签 */
 export const xmlAddPartName = (xml: string) => {
 	if (!xml) return "";
+	console.time('解析xml 耗时')
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	console.timeEnd('解析xml 耗时')
 	const scoreParts = Array.from(xmlParse.getElementsByTagName("score-part"));
 	for (const scorePart of scoreParts) {
 		if (scorePart.getElementsByTagName("part-name").length === 0) {
@@ -642,15 +653,19 @@ export const xmlAddPartName = (xml: string) => {
 			scorePart.getElementsByTagName("part-name")[0].textContent = scorePart.getAttribute("id") || "";
 		}
 	}
+	xmlDocRef.value = xmlParse;
 	return new XMLSerializer().serializeToString(xmlParse);
 }
 
 /** 格式化曲谱
  * 1.全休止符的小节,没有音符默认加个全休止符
  */
-export const formatXML = (xml: string, xmlUrl?: string): string => {
+export const formatXML = (xml: string, xmlUrl?: string, resourceType?: string): string => {
 	if (!xml) return "";
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	//console.time('解析xml 耗时7')
+	// const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+	const xmlParse = xmlDocRef.value && resourceType === 'init' ? xmlDocRef.value : new DOMParser().parseFromString(xml, "text/xml");
+	//console.timeEnd('解析xml 耗时7')
 
 	// 声调
 	const fifths = xmlParse.getElementsByTagName("fifths");

+ 50 - 0
src/page-instrument/App.tsx

@@ -126,6 +126,56 @@ export default defineComponent({
       //     event.preventDefault();
       //   }
       // });
+      
+      /**
+       * DNS 解析耗时: domainLookupEnd - domainLookupStart
+       * TCP 连接耗时: connectEnd - connectStart
+       * SSL 安全连接耗时: connectEnd - secureConnectionStart
+       * 网络请求耗时 (TTFB): responseStart - requestStart
+       *数据传输耗时: responseEnd - responseStart
+        *DOM 解析耗时: domInteractive - responseEnd
+        *资源加载耗时: loadEventStart - domContentLoadedEventEnd
+        *First Byte时间: responseStart - domainLookupStart
+        *白屏时间: responseEnd - fetchStart
+        *首次可交互时间: domInteractive - fetchStart
+        *DOM Ready 时间: domContentLoadEventEnd - fetchStart
+        *页面完全加载时间: loadEventStart - fetchStart
+        *http 头部大小: transferSize - encodedBodySize
+        *重定向次数:performance.navigation.redirectCount
+        *重定向耗时: redirectEnd - redirectStart
+       */
+      window.onload = function() {
+        console.log('加载完成')
+        let timing: any = performance.getEntriesByType('navigation')[0] || {};
+        const { domainLookupEnd, domainLookupStart, connectEnd, connectStart, responseStart,
+          requestStart, responseEnd, domInteractive, loadEventStart, domContentLoadedEventEnd,
+          fetchStart, secureConnectionStart, transferSize, encodedBodySize,
+          redirectEnd, redirectStart
+        } = timing
+        //console.log(timing.domInteractive);
+        //console.log(timing.fetchStart);
+        let diff = timing.domInteractive - timing.fetchStart;
+        //console.log("TTI: " + diff);
+        const timeInfo = [
+          { '类型': 'DNS 解析耗时', '耗时': domainLookupEnd - domainLookupStart},
+          { '类型': 'TCP 连接耗时', '耗时': connectEnd - connectStart},
+          { '类型': 'SSL 安全连接耗时', '耗时': connectEnd - secureConnectionStart},
+          { '类型': '网络请求耗时', '耗时': responseStart - requestStart},
+          { '类型': '数据传输耗时', '耗时': responseEnd - responseStart},
+          { '类型': 'DOM 解析耗时', '耗时': domInteractive - responseEnd},
+          { '类型': '资源加载耗时', '耗时': loadEventStart - domContentLoadedEventEnd},
+          { '类型': 'First Byte时间', '耗时': responseStart - domainLookupStart},
+          { '类型': '白屏时间', '耗时': responseEnd - fetchStart},
+          { '类型': '首次可交互时间', '耗时': domInteractive - fetchStart},
+          { '类型': 'DOM Ready 时间', '耗时': domContentLoadedEventEnd - fetchStart},
+          { '类型': '页面完全加载时间', '耗时': loadEventStart - fetchStart},
+          { '类型': 'http 头部大小', '耗时': transferSize - encodedBodySize},
+          { '类型': '重定向次数', '耗时': performance.navigation.redirectCount},
+          { '类型': '重定向耗时', '耗时': redirectEnd - redirectStart},
+        ];
+        console.table(timeInfo);
+        
+      };      
     });
 
     onUnmounted(() => {

+ 23 - 3
src/page-instrument/view-detail/index.tsx

@@ -119,6 +119,7 @@ export default defineComponent({
         }
       }
     };
+    console.time('加载过程')
     onBeforeMount(async () => {
       // console.time("渲染加载耗时");
       api_keepScreenLongLight();
@@ -215,10 +216,29 @@ export default defineComponent({
 
     /** 渲染完成 */
     const handleRendered = (osmd: any) => {
+      state.isLoading = false
       api_cloudLoading();
       console.timeEnd("渲染加载耗时");
+      console.timeLog('加载过程','谱面渲染完成')
+
       detailData.skeletonLoading = false;
       state.osmd = osmd;
+      // 预览模式不需要往下执行
+      if (state.isPreView) {
+        // 酷乐秀曲谱详情页,需要下载A4尺寸的图片
+        setTimeout(() => {
+          if (query.downPng === 'A4' && state.partIndex != 999) {
+            const imgList = getSvgPngToSize(state.osmd)
+            console.log('A4', imgList)
+            window.parent.postMessage({
+              api: 'musicStaffRender',
+              loading: false,
+              osmdImg: imgList
+            }, '*');
+          }
+        }, 100);
+      return;
+    }
       // 没有设置速度使用读取的速度
       if (state.originSpeed === 0) {
         state.originSpeed = state.speed = (osmd as any).bpm || osmd.Sheet.userStartTempoInBPM || 100;
@@ -620,9 +640,9 @@ export default defineComponent({
             </div>
           )}
         </div>
-        {/* 曲目渲染完成,再去下载mp3资源 */}
-        {!detailData.isLoading && !detailData.skeletonLoading && <AudioList />}
-
+        {/* 曲目渲染时,同步下载mp3资源 */}
+        {!detailData.isLoading && <AudioList />}
+        {/* {!detailData.isLoading && !detailData.skeletonLoading && <AudioList />} */}
         {/* {!detailData.isLoading && <TheAudio src={tickWav} />} */}
 
         {/* 预加载延迟检测组建 */}

+ 36 - 8
src/state.ts

@@ -17,13 +17,14 @@ import { followData, skipNotePractice } from "/src/view/follow-practice/index"
 import { changeSongSourceByBeat } from "/src/view/audio-list"
 import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTime, moveTranslateXNum, destroySmoothAnimation, calcClientWidth } from "/src/page-instrument/view-detail/smoothAnimation"
 import { storeData } from "/src/store";
-import { downloadXmlStr } from "./view/music-score"
+import { downloadXmlStr, xmlDocRef } from "./view/music-score"
 import { musicScoreRef, headerColumnHide } from "/src/page-instrument/view-detail/index"
 import { headTopData } from "/src/page-instrument/header-top/index";
 import { api_lessonTrainingTrainingStudentDetail } from "/src/page-instrument/api"
 import { undoData, moveData } from "/src/view/plugins/move-music-score"
 import { HANDLE_WORK_ADD } from "/src/page-instrument/custom-plugins/work-index";
 import { speedBeatTo, unitImgs } from "/src/helpers/beatConfig"
+import IndexedDBService from "/src/utils/indexedDB";
 import { musicalInstrumentCodeInfo, instruments, fixInstrumentNameCode } from "/src/constant/instruments";
 
 const query: any = getQuery();
@@ -352,7 +353,7 @@ const state = reactive({
   // 加载条
   isLoading: true,
   /** 加载中的文案 */
-  loadingText: '音频资源加载中,请稍后…',
+  loadingText: '资源加载中,请稍后…',
   /** 是否是简单的单行谱模式页面 */
   isSimplePage: false, 
   /** xml的速度和后台设置的速度,计算出的基础音频播放倍率 */
@@ -398,6 +399,8 @@ const state = reactive({
   isWebAudit: false,
   /** 是否是单声轨多声部的声轨 */
   isSingleMutliTrack: false,
+  /** 是否是来源于缓存的xml */
+  xmlFromStore: false,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -664,7 +667,7 @@ export const skipNotePlay = async (itemIndex: number, isStart = false, handType?
 export const togglePlay = async (playState: "play" | "paused", isForceCLoseToast?:boolean) => {
   // 如果mp3资源还在加载中,给出提示
   if (!state.isAppPlay && !state.audioDone) {
-    if (!isForceCLoseToast) showToast('音频资源加载中,请稍后')
+    if (!isForceCLoseToast) showToast('资源加载中,请稍后')
     return
   }
   // 播放之前  当为评测模式和不为MIDI时候按  是否禁用节拍器  切换音源
@@ -1282,6 +1285,25 @@ const initInstrumentCode = async () => {
   // console.log('声轨codes',instruments)
 }
 
+// 判断有没有xml缓存,有则直接使用
+const queryMusicXml = async (id: string, xmlUr: string) => {
+  let xmlString = ''
+  const dbService = new IndexedDBService("MyDatabase", "MyStore");
+  console.time('缓存获取xml')
+  const storeXmlData: any = await dbService.get(id).then((data) => data );
+  if (storeXmlData && storeXmlData.xmlString) {
+    xmlString = storeXmlData && storeXmlData.xmlString
+    state.xmlFromStore = true;
+    console.timeEnd('缓存获取xml')
+    // 使用完后删除数据
+    dbService.delete(id)
+  } else {
+    state.xmlFromStore = false;
+    xmlString = await fetch(xmlUr).then((response) => response.text());
+  }
+  return xmlString;
+}
+
 const getMusicInfo = async (res: any) => {
   try {
     await initInstrumentCode()
@@ -1292,10 +1314,11 @@ const getMusicInfo = async (res: any) => {
   state.isScoreRender = res.data?.isScoreRender
   // 是否默认显示总谱
   state.defaultScoreRender = res.data?.defaultScoreRender
-  /* 获取声轨列表 */
-  let xmlString = await fetch(res.data.xmlFileUrl).then((response) => response.text());
+  // let xmlString = await fetch(res.data.xmlFileUrl).then((response) => response.text());
+  let xmlString: string = await queryMusicXml(res.data.bizId + "", res.data.xmlFileUrl);
   xmlString = xmlAddPartName(xmlString);
   downloadXmlStr.value = xmlString //给musice-score 赋值xmlString 以免加载2次
+    /* 获取声轨列表 */
   const tracks = xmlToTracks(xmlString) //获取声轨列表
   // 是否显示节拍器  (管乐迷 默认显示节拍器)
   state.isMixBeat = res.data?.isMixBeat  
@@ -1355,9 +1378,14 @@ const getMusicInfo = async (res: any) => {
 };
 //获取xml中的音轨数据
 function xmlToTracks(xmlString: string) {
-  const xmlParse = new DOMParser().parseFromString(xmlString, "text/xml");
-  const partNames = Array.from(xmlParse.getElementsByTagName('part-name'));
-  return partNames.reduce((arr: string[], item) => {
+  //console.time('domparse')
+  // console.time('解析xml 耗时1')
+  // const xmlParse = new DOMParser().parseFromString(xmlString, "text/xml");
+  const xmlParse = xmlDocRef.value;
+  // console.timeEnd('解析xml 耗时1')
+  //console.timeEnd('domparse')
+  const partNames = xmlParse ? Array.from(xmlParse.getElementsByTagName('part-name')) : [];
+  return partNames.reduce((arr: string[], item: any) => {
     const textContent = item?.textContent?.trim()
     if (textContent?.toLocaleLowerCase() === "common") {
       (window as any).HasCommonTrack = true;

+ 117 - 0
src/utils/indexedDB.ts

@@ -0,0 +1,117 @@
+interface DataInfo {
+    id: number | string; // 主键
+    xmlString: string;
+    xmlDoc: Document;
+}
+
+export default class IndexedDBService<T> {
+    private dbName: string;
+    private storeName: string;
+    private version: number;
+  
+    constructor(dbName: string, storeName: string, version: number = 1) {
+      this.dbName = dbName;
+      this.storeName = storeName;
+      this.version = version;
+    }
+  
+    // 初始化数据库
+    private async init(): Promise<IDBDatabase> {
+      return new Promise((resolve, reject) => {
+        const request = indexedDB.open(this.dbName, this.version);
+  
+        // 数据库第一次创建或版本号升级时触发
+        request.onupgradeneeded = () => {
+          const db = request.result;
+          // 创建对象存储(类似于表)
+          if (!db.objectStoreNames.contains(this.storeName)) {
+            db.createObjectStore(this.storeName, { keyPath: "id" });
+          }
+        };
+  
+        // 返回数据库实例
+        request.onsuccess = () => resolve(request.result);
+        // 捕获错误
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 添加或者更新数据
+    async save(data: T): Promise<void> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readwrite");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.put(data);
+  
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 获取某条数据
+    async get(id: number | string): Promise<T | undefined> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readonly");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.get(id);
+  
+        request.onsuccess = () => resolve(request.result as T);
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 删除数据
+    async delete(id: number | string): Promise<void> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readwrite");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.delete(id);
+  
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+  
+    // 查询所有数据
+    async getAll(): Promise<T[]> {
+      const db = await this.init();
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readonly");
+        const store = transaction.objectStore(this.storeName);
+        const request = store.getAll();
+  
+        request.onsuccess = () => resolve(request.result as T[]);
+        request.onerror = () => reject(request.error);
+      });
+    }
+
+    // 清空某个 Object Store 中的所有数据
+    async clearAllData(): Promise<void> {
+      const db = await this.init();
+    
+      return new Promise((resolve, reject) => {
+        const transaction = db.transaction(this.storeName, "readwrite");
+        const store = transaction.objectStore(this.storeName);
+    
+        const request = store.clear(); // 清空数据
+    
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+
+    // 删除整个数据库
+    async deleteDatabase(): Promise<void> {
+      return new Promise((resolve, reject) => {
+        const request = indexedDB.deleteDatabase(this.dbName); // 删除整个数据库
+    
+        request.onsuccess = () => resolve();
+        request.onerror = () => reject(request.error);
+      });
+    }
+}
+  
+  

+ 8 - 5
src/view/audio-list/index.tsx

@@ -214,7 +214,8 @@ const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
 		return Promise.resolve(null)
 	}
 	return new Promise((resolve) => {
-		const a = new Audio(src + '?v=' + Date.now());
+		// const a = new Audio(src + '?v=' + Date.now());
+		const a = new Audio(src);
 		a.onloadedmetadata = () => {
 			resolve(a);
 		};
@@ -381,7 +382,7 @@ export async function changeCombineAudio (combineIndex: number){
 		}
 		return
 	}
-	state.loadingText = "音频资源加载中,请稍后…";
+	state.loadingText = "资源加载中,请稍后…";
 	state.isLoading = true;
 	const musicUrl = audioData.combineMusics[combineIndex]
 	// 有就拿缓存,没有就加载
@@ -552,7 +553,7 @@ export default defineComponent({
 				return
 			}			
 			if (state.playMode !== "MIDI") {
-				console.time("音频加载时间123")
+				console.time("音频加载时")
 				// 处理音源
 				const [music, accompany, fanSong, banSong, mingSong, mingSongGirl] = await loadAudio()
 				audioData.backgroundEle = accompany;
@@ -645,8 +646,9 @@ export default defineComponent({
 				changeMingSongType()
 
 				state.audioDone = true;
-				state.isLoading = false
-				console.timeEnd("音频加载时间123")
+				// state.isLoading = false
+				console.timeEnd("音频加载耗时")
+				console.timeLog('加载过程','音频加载完成')
 				console.log("音频数据:",audioData)
 				api_playProgress(progress);
 			} else {
@@ -659,6 +661,7 @@ export default defineComponent({
 				// 监听midi播放结束
 				api_cloudplayed(midiPlayEnd);
 			}
+			console.timeEnd('加载过程')
 		});
 		onUnmounted(() => {
 			api_remove_cloudplayed(midiPlayEnd);

+ 23 - 4
src/view/music-score/index.tsx

@@ -35,6 +35,9 @@ export const resetRenderMusicScore = (type?: string) => {
 // 下载后的xml
 export const downloadXmlStr = ref("")
 
+// xml的document对象
+export const xmlDocRef = ref<Document | null>(null);
+
 export default defineComponent({
 	name: "music-score",
 	emits: ["rendered"],
@@ -69,18 +72,34 @@ export default defineComponent({
 				state.musicRenderType = musicRenderType;
 			}
 		};
-		const getXML = async () => {
+		const getXML = async (cbType?: string) => {
 			// 当有下载的xml的时候直接使用,否则需要下载
 			if(!downloadXmlStr.value){
 				downloadXmlStr.value = await fetch(state.xmlUrl).then((response) => response.text())
 			}
+			console.time('增删改查xml')
+			// if (state.xmlFromStore) {
+			// 	musicData.score = state.isCombineRender ? downloadXmlStr.value : onlyVisible(downloadXmlStr.value, state.partIndex);
+			// 	if (state.gradualTimes) {
+			// 		state.gradual = getGradualLengthByXml(downloadXmlStr.value);
+			// 	}
+			// } else {
+			// 	const xmlStr = downloadXmlStr.value;
+			// 	const parseXmlInfo = getCustomInfo(xmlStr, cbType);
+			// 	const xml = formatXML(parseXmlInfo.parsedXML, '', cbType);
+			// 	musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
+			// 	if (state.gradualTimes) {
+			// 		state.gradual = getGradualLengthByXml(xml);
+			// 	}
+			// }
 			const xmlStr = downloadXmlStr.value;
-			const parseXmlInfo = getCustomInfo(xmlStr);
-			const xml = formatXML(parseXmlInfo.parsedXML);
+			const parseXmlInfo = getCustomInfo(xmlStr, cbType);
+			const xml = formatXML(parseXmlInfo.parsedXML, '', cbType);
 			musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
 			if (state.gradualTimes) {
 				state.gradual = getGradualLengthByXml(xml);
 			}
+			console.timeEnd('增删改查xml')
 		};
 
 		const init = async () => {
@@ -191,7 +210,7 @@ export default defineComponent({
 		let horizontalDragScroll:HorizontalDragScroll | null
 		onMounted(async () => {
 			//setRenderType();
-			await getXML();
+			await getXML('init');
 			await init();
 			// pc 端支持 拖动滚动
 			if(state.platform === "PC" || query.isCbs){

+ 12 - 1
src/view/plugins/toggleMusicSheet/index.tsx

@@ -11,6 +11,8 @@ import Dragbom from "/src/view/plugins/useDrag/dragbom";
 import { setGuidance } from "/src/page-instrument/custom-plugins/guide-page/api";
 import { storeData } from "/src/store";
 import { api_closeCamera } from "/src/helpers/communication";
+import { xmlDocRef, downloadXmlStr } from "/src/view/music-score"
+import IndexedDBService from "/src/utils/indexedDB";
 
 export const toggleMusicSheet = reactive({
   show: false,
@@ -52,7 +54,15 @@ export default defineComponent({
       }
     })
 
-    const switchMusic = (partIndexs: number[]) => {
+    // 切换的时候存储处理过后的xml
+    const storeXmlData = async () => {
+      const dbService = new IndexedDBService("MyDatabase", "MyStore");
+      await dbService.save({ id: state.examSongId, xmlString: downloadXmlStr.value })
+        .then(() => dbService.get(state.examSongId))
+        .then((data) => console.log("数据:", data));
+    }
+
+    const switchMusic = async (partIndexs: number[]) => {
       const index = partIndexs.join(",") as any
       // 暂停播放
       togglePlay("paused");
@@ -85,6 +95,7 @@ export default defineComponent({
       if (state.setting.camera) {
         api_closeCamera();
       }
+      await storeXmlData()
       location.href = _url
     }
 

+ 4 - 1
src/view/selection/index.tsx

@@ -277,11 +277,14 @@ export default defineComponent({
 			}
 			return []
 		})
-
+		console.time('dom挂载')
 		onMounted(() => {
+			console.timeEnd('dom挂载')
 			selectData.notes = [];
 			selectData.staves = [];
+			console.time('添加dom时间')
 			calcNoteData();
+			console.timeEnd('添加dom时间')
 			const img: HTMLElement = document.querySelector('#cursorImg-0')!
 			if (metronomeData.cursorMode === 2){
 				img.classList.add('lineHide')

+ 29 - 0
vite.config.ts

@@ -14,6 +14,16 @@ export default defineConfig({
   // assetsInclude: ['**/*.html'],
   plugins: [
     // mkcert(), // 本地https
+    // viteCompression({
+    //   algorithm: "gzip", // 指定压缩算法为gzip,[ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
+    //   ext: ".gz", // 指定压缩后的文件扩展名为.gz
+    //   threshold: 10240, // 仅对文件大小大于threshold的文件进行压缩,默认为10KB
+    //   deleteOriginFile: false, // 是否删除原始文件,默认为false
+    //   filter: /\.(js|css|json|html|ico|svg)(\?.*)?$/i, // 匹配要压缩的文件的正则表达式,默认为匹配.js、.css、.json、.html、.ico和.svg文件
+    //   compressionOptions: { level: 9 }, // 指定gzip压缩级别,默认为9(最高级别)
+    //   verbose: true, //是否在控制台输出压缩结果
+    //   disable: false, //是否禁用插件
+    // }),    
     legacy({
       targets: ["Chrome 63"],
       additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
@@ -35,11 +45,30 @@ export default defineConfig({
     },
   },
   build: {
+    minify: 'terser', // 启用 terser 压缩  
+    terserOptions: {  
+        compress: {  
+            // pure_funcs: ['console.log'], // 只删除 console.log 
+            //drop_console: true, // 删除所有 console
+            drop_debugger: true, // 删除 debugger  
+        }  
+    },    
     rollupOptions: {
       input: {
         instrument: resolve(__dirname, "instrument.html"),
       },
       output: {
+        // 最小化拆分包
+        manualChunks(id) {
+          if (id.includes("osmd-extended")) {
+            // 通过拆分包的方式将所有来自osmd-extended的模块打包到单独的chunk中
+            return id
+              .toString()
+              .split("osmd-extended/")[1]
+              .split("/")[0]
+              .toString();
+          }
+        },        
         chunkFileNames: "js/[name]-[hash].js", // 引入文件名的名称
         entryFileNames: "js/[name]-[hash].js", // 包的入口文件名称
         assetFileNames: "[ext]/[name]-[hash].[ext]", // 资源文件像 字体,图片等