|  | @@ -625,49 +625,112 @@ export default defineComponent({
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** 生成曲谱节拍器数据 */
 | 
	
		
			
				|  |  | -    const productMetronomeData = () => {
 | 
	
		
			
				|  |  | +    const productMetronomeData = async () => {
 | 
	
		
			
				|  |  |        const times = new ABCJS.TimingCallbacks(abcData.visualObj);
 | 
	
		
			
				|  |  |        data.times = times.noteTimings;
 | 
	
		
			
				|  |  |        const list: any[] = [];
 | 
	
		
			
				|  |  |        let meter = abcData.abc.meter || "";
 | 
	
		
			
				|  |  | -      // length - 1是为了去除最后一个空的结束事件
 | 
	
		
			
				|  |  | -      for (let i = 0; i < times.noteTimings.length - 1; i++) {
 | 
	
		
			
				|  |  | -        const timeNote = times.noteTimings[i];
 | 
	
		
			
				|  |  | -        const abcNote = getNextNote(timeNote.startChar as number);
 | 
	
		
			
				|  |  | -        let indexStr: any = abcNote.chord?.find((n) => n.position === "left")?.name || "";
 | 
	
		
			
				|  |  | -        indexStr = indexStr.split(".").map((n: string) => Number(n));
 | 
	
		
			
				|  |  | -        if (indexStr.length === 2) {
 | 
	
		
			
				|  |  | -          const measure = abcData.abc.measures[indexStr[0]];
 | 
	
		
			
				|  |  | -          // 如果小节里面有拍号,后面一直延用这个拍号,以此类推, ps: 下个版本
 | 
	
		
			
				|  |  | -          // if (measure.meter){
 | 
	
		
			
				|  |  | -          // 	meter = measure.meter;
 | 
	
		
			
				|  |  | -          // }
 | 
	
		
			
				|  |  | -          const reg = new RegExp(/M:(\d+)\/\d+/);
 | 
	
		
			
				|  |  | -          const numerator = Number(meter.match(reg)?.[1]);
 | 
	
		
			
				|  |  | -          // console.log("🚀 ~ reg:", meter.match(reg)?.[1], abcData.abc.meter)
 | 
	
		
			
				|  |  | -          // console.log("🚀 ~ measure:", measure)
 | 
	
		
			
				|  |  | -          const note = measure.notes[indexStr[1]];
 | 
	
		
			
				|  |  | -          list.push({
 | 
	
		
			
				|  |  | -            ...note,
 | 
	
		
			
				|  |  | -            timeNote,
 | 
	
		
			
				|  |  | -            abcNote,
 | 
	
		
			
				|  |  | -            measure: {
 | 
	
		
			
				|  |  | -              numerator,
 | 
	
		
			
				|  |  | -            },
 | 
	
		
			
				|  |  | -          });
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      // console.log("abcData.abc.measures", list);
 | 
	
		
			
				|  |  | -      if (!metronomeData.metro) {
 | 
	
		
			
				|  |  | -        metronomeData.metro = new Metronome();
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      try {
 | 
	
		
			
				|  |  | -        metronomeData.activeIndex = -1;
 | 
	
		
			
				|  |  | -        metronomeData.metro.init(list);
 | 
	
		
			
				|  |  | -      } catch (error) {
 | 
	
		
			
				|  |  | -        console.log("🚀 ~ 生成节拍器数据错误:", error);
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // 分帧计算:每帧处理 100 条数据(可根据性能调整)
 | 
	
		
			
				|  |  | +      const chunkSize = 5;
 | 
	
		
			
				|  |  | +      const totalItems = times.noteTimings.length - 1; // 去掉最后一个空事件
 | 
	
		
			
				|  |  | +      let currentIndex = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // 使用 Promise 包装,方便 await 等待计算完成
 | 
	
		
			
				|  |  | +      await new Promise<void>((resolve) => {
 | 
	
		
			
				|  |  | +        const processChunk = () => {
 | 
	
		
			
				|  |  | +          const endIndex = Math.min(currentIndex + chunkSize, totalItems);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // 处理当前 chunk 的数据
 | 
	
		
			
				|  |  | +          for (let i = currentIndex; i < endIndex; i++) {
 | 
	
		
			
				|  |  | +            const timeNote = times.noteTimings[i];
 | 
	
		
			
				|  |  | +            const abcNote = getNextNote(timeNote.startChar as number);
 | 
	
		
			
				|  |  | +            let indexStr: any = abcNote.chord?.find((n) => n.position === "left")?.name || "";
 | 
	
		
			
				|  |  | +            indexStr = indexStr.split(".").map((n: string) => Number(n));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (indexStr.length === 2) {
 | 
	
		
			
				|  |  | +              const measure = abcData.abc.measures[indexStr[0]];
 | 
	
		
			
				|  |  | +              const reg = new RegExp(/M:(\d+)\/\d+/);
 | 
	
		
			
				|  |  | +              const numerator = Number(meter.match(reg)?.[1]);
 | 
	
		
			
				|  |  | +              const note = measure.notes[indexStr[1]];
 | 
	
		
			
				|  |  | +              list.push({
 | 
	
		
			
				|  |  | +                ...note,
 | 
	
		
			
				|  |  | +                timeNote,
 | 
	
		
			
				|  |  | +                abcNote,
 | 
	
		
			
				|  |  | +                measure: {
 | 
	
		
			
				|  |  | +                  numerator,
 | 
	
		
			
				|  |  | +                },
 | 
	
		
			
				|  |  | +              });
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          currentIndex = endIndex;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // 如果还有数据,继续下一帧处理
 | 
	
		
			
				|  |  | +          if (currentIndex < totalItems) {
 | 
	
		
			
				|  |  | +            requestAnimationFrame(processChunk);
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            // 所有数据处理完成,初始化 Metronome
 | 
	
		
			
				|  |  | +            if (!metronomeData.metro) {
 | 
	
		
			
				|  |  | +              metronomeData.metro = new Metronome();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            try {
 | 
	
		
			
				|  |  | +              metronomeData.activeIndex = -1;
 | 
	
		
			
				|  |  | +              metronomeData.metro.init(list);
 | 
	
		
			
				|  |  | +            } catch (error) {
 | 
	
		
			
				|  |  | +              console.log("🚀 ~ 生成节拍器数据错误:", error);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            resolve(); // 完成 Promise
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 开始处理
 | 
	
		
			
				|  |  | +        requestAnimationFrame(processChunk);
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  | +    // const productMetronomeData = () => {
 | 
	
		
			
				|  |  | +    //   const times = new ABCJS.TimingCallbacks(abcData.visualObj);
 | 
	
		
			
				|  |  | +    //   data.times = times.noteTimings;
 | 
	
		
			
				|  |  | +    //   const list: any[] = [];
 | 
	
		
			
				|  |  | +    //   let meter = abcData.abc.meter || "";
 | 
	
		
			
				|  |  | +    //   // length - 1是为了去除最后一个空的结束事件
 | 
	
		
			
				|  |  | +    //   for (let i = 0; i < times.noteTimings.length - 1; i++) {
 | 
	
		
			
				|  |  | +    //     const timeNote = times.noteTimings[i];
 | 
	
		
			
				|  |  | +    //     const abcNote = getNextNote(timeNote.startChar as number);
 | 
	
		
			
				|  |  | +    //     let indexStr: any = abcNote.chord?.find((n) => n.position === "left")?.name || "";
 | 
	
		
			
				|  |  | +    //     indexStr = indexStr.split(".").map((n: string) => Number(n));
 | 
	
		
			
				|  |  | +    //     if (indexStr.length === 2) {
 | 
	
		
			
				|  |  | +    //       const measure = abcData.abc.measures[indexStr[0]];
 | 
	
		
			
				|  |  | +    //       // 如果小节里面有拍号,后面一直延用这个拍号,以此类推, ps: 下个版本
 | 
	
		
			
				|  |  | +    //       // if (measure.meter){
 | 
	
		
			
				|  |  | +    //       // 	meter = measure.meter;
 | 
	
		
			
				|  |  | +    //       // }
 | 
	
		
			
				|  |  | +    //       const reg = new RegExp(/M:(\d+)\/\d+/);
 | 
	
		
			
				|  |  | +    //       const numerator = Number(meter.match(reg)?.[1]);
 | 
	
		
			
				|  |  | +    //       // console.log("🚀 ~ reg:", meter.match(reg)?.[1], abcData.abc.meter)
 | 
	
		
			
				|  |  | +    //       // console.log("🚀 ~ measure:", measure)
 | 
	
		
			
				|  |  | +    //       const note = measure.notes[indexStr[1]];
 | 
	
		
			
				|  |  | +    //       list.push({
 | 
	
		
			
				|  |  | +    //         ...note,
 | 
	
		
			
				|  |  | +    //         timeNote,
 | 
	
		
			
				|  |  | +    //         abcNote,
 | 
	
		
			
				|  |  | +    //         measure: {
 | 
	
		
			
				|  |  | +    //           numerator,
 | 
	
		
			
				|  |  | +    //         },
 | 
	
		
			
				|  |  | +    //       });
 | 
	
		
			
				|  |  | +    //     }
 | 
	
		
			
				|  |  | +    //   }
 | 
	
		
			
				|  |  | +    //   // console.log("abcData.abc.measures", list);
 | 
	
		
			
				|  |  | +    //   if (!metronomeData.metro) {
 | 
	
		
			
				|  |  | +    //     metronomeData.metro = new Metronome();
 | 
	
		
			
				|  |  | +    //   }
 | 
	
		
			
				|  |  | +    //   try {
 | 
	
		
			
				|  |  | +    //     metronomeData.activeIndex = -1;
 | 
	
		
			
				|  |  | +    //     metronomeData.metro.init(list);
 | 
	
		
			
				|  |  | +    //   } catch (error) {
 | 
	
		
			
				|  |  | +    //     console.log("🚀 ~ 生成节拍器数据错误:", error);
 | 
	
		
			
				|  |  | +    //   }
 | 
	
		
			
				|  |  | +    // };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // 高亮选中的音符
 | 
	
		
			
				|  |  |      const rangeHighlight = (startChar: number) => {
 | 
	
	
		
			
				|  | @@ -726,7 +789,15 @@ export default defineComponent({
 | 
	
		
			
				|  |  |      const handleClickExit = async () => {
 | 
	
		
			
				|  |  |        if (data.saveLoading) return;
 | 
	
		
			
				|  |  |        const msg = message.loading("保存中...", { duration: 0 });
 | 
	
		
			
				|  |  | -      await handleSaveMusic(false);
 | 
	
		
			
				|  |  | +      const result = await handleSaveMusic(false);
 | 
	
		
			
				|  |  | +      if(result === 'noName') {
 | 
	
		
			
				|  |  | +        msg.destroy();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        message.destroyAll();
 | 
	
		
			
				|  |  | +        message.error("请输入曲谱名称");
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  | +      console.log(result, "result")
 | 
	
		
			
				|  |  |        setTimeout(async () => {
 | 
	
		
			
				|  |  |          msg.type = "success";
 | 
	
		
			
				|  |  |          msg.content = "保存成功";
 | 
	
	
		
			
				|  | @@ -1535,7 +1606,7 @@ export default defineComponent({
 | 
	
		
			
				|  |  |            message.destroyAll();
 | 
	
		
			
				|  |  |            message.error("请输入曲谱名称");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        return
 | 
	
		
			
				|  |  | +        return 'noName';
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        if (musicLock) return;
 | 
	
		
			
				|  |  |        musicLock = true;
 | 
	
	
		
			
				|  | @@ -1761,6 +1832,7 @@ export default defineComponent({
 | 
	
		
			
				|  |  |          rect.setAttribute("fill", "#fff");
 | 
	
		
			
				|  |  |          svg.prepend(rect);
 | 
	
		
			
				|  |  |          if (svg) {
 | 
	
		
			
				|  |  | +          console.log(svg.outerHTML, 'svg.outerHTML')
 | 
	
		
			
				|  |  |            const _canvas = svg2canvas(svg.outerHTML);
 | 
	
		
			
				|  |  |            if (isUrl) {
 | 
	
		
			
				|  |  |              // document.body.appendChild(_canvas);
 | 
	
	
		
			
				|  | @@ -1774,8 +1846,13 @@ export default defineComponent({
 | 
	
		
			
				|  |  |              el.dispatchEvent(event);
 | 
	
		
			
				|  |  |            } else {
 | 
	
		
			
				|  |  |              _canvas.toBlob(async (blob) => {
 | 
	
		
			
				|  |  | -              const pngUrl = await api_uploadFile(blob, data.musicId + ".png");
 | 
	
		
			
				|  |  | -              resolve(pngUrl);
 | 
	
		
			
				|  |  | +              if(blob) {
 | 
	
		
			
				|  |  | +                const pngUrl = await api_uploadFile(blob, data.musicId + ".png");
 | 
	
		
			
				|  |  | +                resolve(pngUrl);
 | 
	
		
			
				|  |  | +              } else {
 | 
	
		
			
				|  |  | +                // reject('bolb is null')
 | 
	
		
			
				|  |  | +                resolve('')
 | 
	
		
			
				|  |  | +              }
 | 
	
		
			
				|  |  |              }, "image/png");
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -1832,7 +1909,14 @@ export default defineComponent({
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      const downXML = async () => {
 | 
	
		
			
				|  |  |        const msg = message.loading("导出中...");
 | 
	
		
			
				|  |  | -      await handleSaveMusic(false);
 | 
	
		
			
				|  |  | +      const result = await handleSaveMusic(false);
 | 
	
		
			
				|  |  | +      if(result === 'noName') {
 | 
	
		
			
				|  |  | +        msg.destroy();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        message.destroyAll();
 | 
	
		
			
				|  |  | +        message.error("请输入曲谱名称");
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  |        const res = await getDetailData();
 | 
	
		
			
				|  |  |        if (!res?.data?.xml) {
 | 
	
		
			
				|  |  |          msg.type = "error";
 | 
	
	
		
			
				|  | @@ -1882,6 +1966,13 @@ export default defineComponent({
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          const file = e.target.files[0];
 | 
	
		
			
				|  |  |          if (val === "xml") {
 | 
	
		
			
				|  |  | +          const size = file.size || 0
 | 
	
		
			
				|  |  | +          const isLt2M = size / 1024 / 1024 < 3;
 | 
	
		
			
				|  |  | +          if (!isLt2M) {
 | 
	
		
			
				|  |  | +            message.error(`文件大小不能超过3M`);
 | 
	
		
			
				|  |  | +            data.loadingAudioSrouce2 = false;
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  |            const reader = new FileReader();
 | 
	
		
			
				|  |  |            reader.onload = async (e: any) => {
 | 
	
		
			
				|  |  |              // let abc = e.target.result;
 | 
	
	
		
			
				|  | @@ -1994,6 +2085,7 @@ export default defineComponent({
 | 
	
		
			
				|  |  |          let abc = reuslt;
 | 
	
		
			
				|  |  |          abc = new DOMParser().parseFromString(abc, "text/xml");
 | 
	
		
			
				|  |  |          // // console.log("🚀 ~ abc:", abc);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          abc = (window as any).vertaal(abc, { p: "f", t: 1, u: 0, v: 3, mnum: 0 });
 | 
	
		
			
				|  |  |          // console.log('abc', abc);
 | 
	
		
			
				|  |  |          const parseData = ABCJS.renderAbc("importRef", abc[0], { responsive: "resize" });
 | 
	
	
		
			
				|  | @@ -2008,14 +2100,16 @@ export default defineComponent({
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // 取消
 | 
	
		
			
				|  |  |      const onCancelExport = async () => {
 | 
	
		
			
				|  |  | -      data = Object.assign(data, importTemp.value.data);
 | 
	
		
			
				|  |  | -      abcData = Object.assign(abcData, importTemp.value.abcData);
 | 
	
		
			
				|  |  | -      clearInterval(importTemp.value.timer);
 | 
	
		
			
				|  |  | -      // 判断是否有编号,有则取消
 | 
	
		
			
				|  |  | -      if (importTemp.value.importFileId) {
 | 
	
		
			
				|  |  | -        await api_musicalScoreConversionRecordRemove(importTemp.value.importFileId);
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      data.loadingAudioSrouce2 = false;
 | 
	
		
			
				|  |  | +      requestAnimationFrame(async () => {
 | 
	
		
			
				|  |  | +        data = Object.assign(data, importTemp.value.data);
 | 
	
		
			
				|  |  | +        abcData = Object.assign(abcData, importTemp.value.abcData);
 | 
	
		
			
				|  |  | +        clearInterval(importTemp.value.timer);
 | 
	
		
			
				|  |  | +        // 判断是否有编号,有则取消
 | 
	
		
			
				|  |  | +        if (importTemp.value.importFileId) {
 | 
	
		
			
				|  |  | +          await api_musicalScoreConversionRecordRemove(importTemp.value.importFileId);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        data.loadingAudioSrouce2 = false;
 | 
	
		
			
				|  |  | +      })
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /** 设置选段小节 */
 | 
	
	
		
			
				|  | @@ -2057,7 +2151,8 @@ export default defineComponent({
 | 
	
		
			
				|  |  |        const query = getQuery();
 | 
	
		
			
				|  |  |        // 判断是否有id,如果没有则先保存
 | 
	
		
			
				|  |  |        if (!query.id) {
 | 
	
		
			
				|  |  | -        await handleSaveMusic(true);
 | 
	
		
			
				|  |  | +        const result = await handleSaveMusic(true);
 | 
	
		
			
				|  |  | +        if(result === 'noName') return
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        const query2 = getQuery();
 | 
	
		
			
				|  |  |        const res = await api_musicSheetCreationDetail(query2.id);
 |