Bläddra i källkod

Merge branch 'iteration-20231220'

lex 1 år sedan
förälder
incheckning
d2393dd049

+ 298 - 280
src/pc/component/upload-to-resources/index.tsx

@@ -1,269 +1,287 @@
 import { defineComponent, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
-import {
-	api_musicSheetCreationWav2mp3,
-	api_musicSheetCreationSaveMusic,
-	api_subjectList,
-} from "../../api";
-import {
-	NButton,
-	NForm,
-	NFormItem,
-	NIcon,
-	NModal,
-	NProgress,
-	NSelect,
-	NSpace,
-	useMessage,
-} from "naive-ui";
+import ABCJS, { AbcElem, AbcVisualParams, ClickListenerAnalysis, ClickListenerDrag, NoteTimingEvent, SynthObjectController } from "abcjs";
+import { api_musicSheetCreationWav2mp3, api_musicSheetCreationSaveMusic, api_subjectList, api_musicSheetCreationUpdate } from "../../api";
+import { NButton, NForm, NFormItem, NIcon, NModal, NProgress, NSelect, NSpace, useMessage } from "naive-ui";
 import styles from "./index.module.less";
 import { Close } from "@vicons/ionicons5";
 import { SelectMixedOption } from "naive-ui/es/select/src/interface";
 import { api_uploadFile } from "/src/utils/uploadFile";
+import { bufferToWave } from "/src/helpers/parseABC";
+import { decodeUrl, downloadFile } from "/src/utils";
+import { renderMeasures } from "../../home/runtime";
+import cleanDeep from "clean-deep";
 
 export default defineComponent({
-	name: "UploadToResources",
-	props: {
-		show: {
-			type: Boolean,
-			default: false,
-		},
-		item: {
-			type: Object,
-			default: () => ({}),
-		},
-	},
-	emits: ["update:show", "success"],
-	setup(props, { emit }) {
-		const message = useMessage();
-		const model = reactive({
-			subjects: [] as SelectMixedOption[],
-			saveLoading: false,
-			saveProgress: 0,
-			productOpen: false,
-			productIfameSrc: "",
-		});
-		const froms = reactive({
-			subjectId: null,
-			isPublic: 0,
-			mp3: "",
-			musicImg: "",
-			musicSvg: "",
-			musicJianSvg: "",
-		});
-		const getSubjects = async () => {
-			const { data } = await api_subjectList();
-			model.subjects = data.map((item: any) => {
-				return {
-					label: item.name,
-					value: item.id,
-				};
-			});
-		};
+  name: "UploadToResources",
+  props: {
+    show: {
+      type: Boolean,
+      default: false,
+    },
+    item: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  emits: ["update:show", "success"],
+  setup(props, { emit }) {
+    const message = useMessage();
+    const model = reactive({
+      subjects: [] as SelectMixedOption[],
+      saveLoading: false,
+      saveProgress: 0,
+      productOpen: false,
+      productIfameSrc: "",
+    });
+    const froms = reactive({
+      subjectId: null,
+      isPublic: 0,
+      mp3: "",
+      musicImg: "",
+      musicSvg: "",
+      musicJianSvg: "",
+    });
+    const getSubjects = async () => {
+      const { data } = await api_subjectList();
+      model.subjects = data.map((item: any) => {
+        return {
+          label: item.name,
+          value: item.id,
+        };
+      });
+    };
 
-		const handleProductResult = (res: MessageEvent) => {
-			const data = res.data;
-			if (data?.api === "webApi_renderSvg") {
-				let imgs: any = [];
-				try {
-					imgs = JSON.parse(data.product);
-				} catch (error) {
-					console.log("🚀 ~ error:", error);
-				}
-				imgs = imgs.filter((item: any) => item.base64);
-				if (imgs.length === 3) {
-					handleUploadImg(imgs);
-				}
-				console.log("🚀 ~ 上传之前", [...imgs]);
-			}
-		};
-		const handleUploadImg = async (imgs: any[]) => {
-			if (!props.show) return;
-			for (let i = 0; i < imgs.length; i++) {
-				const fileName = `${Date.now()}p${i}.png`;
-				const file = dataURLtoFile(imgs[i].base64, fileName);
-				imgs[i].url = await api_uploadFile(file, fileName, () => {});
-				model.saveProgress = (i + 1) * 20;
-			}
-			froms.musicImg = imgs[0]?.url || "";
-			froms.musicSvg = imgs[1]?.url || "";
-			froms.musicJianSvg = imgs[2]?.url || "";
-			model.productOpen = false;
-			imgs = [];
-			if (!props.show) return;
-			handleSubmit();
-		};
-		/** base64转file */
-		const dataURLtoFile = (dataurl: string, filename: string) => {
-			let arr = dataurl.split(",") || [],
-				mime = arr[0].match(/:(.*?);/)?.[1],
-				bstr = atob(arr[1]),
-				n = bstr.length,
-				u8arr = new Uint8Array(n);
-			while (n--) {
-				u8arr[n] = bstr.charCodeAt(n);
-			}
-			return new File([u8arr], filename, { type: mime });
-		};
-		onMounted(() => {
-			getSubjects();
-			window.addEventListener("message", handleProductResult);
-		});
-		onUnmounted(() => {
-			window.removeEventListener("message", handleProductResult);
-		});
-		watch(
-			() => props.item,
-			() => {
-				console.log(props.item);
-				froms.subjectId = props.item.subjectId ?? null;
-			}
-		);
+    const handleProductResult = (res: MessageEvent) => {
+      const data = res.data;
+      if (data?.api === "webApi_renderSvg") {
+        let imgs: any = [];
+        try {
+          imgs = JSON.parse(data.product);
+        } catch (error) {
+          console.log("🚀 ~ error:", error);
+        }
+        imgs = imgs.filter((item: any) => item.base64);
+        if (imgs.length === 3) {
+          handleUploadImg(imgs);
+        }
+        console.log("🚀 ~ 上传之前", [...imgs]);
+      }
+    };
+    const handleUploadImg = async (imgs: any[]) => {
+      if (!props.show) return;
+      for (let i = 0; i < imgs.length; i++) {
+        const fileName = `${Date.now()}p${i}.png`;
+        const file = dataURLtoFile(imgs[i].base64, fileName);
+        imgs[i].url = await api_uploadFile(file, fileName, () => {});
+        model.saveProgress = (i + 1) * 20;
+      }
+      froms.musicImg = imgs[0]?.url || "";
+      froms.musicSvg = imgs[1]?.url || "";
+      froms.musicJianSvg = imgs[2]?.url || "";
+      model.productOpen = false;
+      imgs = [];
+      if (!props.show) return;
+      handleSubmit();
+    };
+    /** base64转file */
+    const dataURLtoFile = (dataurl: string, filename: string) => {
+      let arr = dataurl.split(",") || [],
+        mime = arr[0].match(/:(.*?);/)?.[1],
+        bstr = atob(arr[1]),
+        n = bstr.length,
+        u8arr = new Uint8Array(n);
+      while (n--) {
+        u8arr[n] = bstr.charCodeAt(n);
+      }
+      return new File([u8arr], filename, { type: mime });
+    };
+    onMounted(() => {
+      getSubjects();
+      window.addEventListener("message", handleProductResult);
+    });
+    onUnmounted(() => {
+      window.removeEventListener("message", handleProductResult);
+    });
+    watch(
+      () => props.item,
+      () => {
+        // console.log(props.item, model.subjects);
+        const subjectId = model.subjects.length > 0 ? model.subjects[0].value : null;
+        froms.subjectId = props.item.subjectId ?? subjectId;
+      }
+    );
 
-		const createMusic = async () => {
-			console.log()
-			await api_musicSheetCreationSaveMusic({
-				musicSheetCreationId: props.item.id,
-				musicSheetName: props.item.name || "曲谱名称",
-				musicSheetCategoriesId: "",
-				audioType: "MP3",
-				mp3Type: "MP3",
-				xmlFileUrl: props.item.xml,
-				musicSubject: froms.subjectId,
-				showFingering: 1,
-				canEvaluate: 1,
-				notation: 1,
-				playSpeed: props.item?.visualObj?.metaText?.tempo?.bpm || "",
-				background: [
-					{
-						audioFileUrl: froms.mp3,
-						track: "P1",
-					},
-				],
-				musicImg: froms.musicImg,
-				musicSvg: froms.musicSvg,
-				musicJianSvg: froms.musicJianSvg,
-				extConfigJson: "",
-			});
-		};
-		const wav2mp3 = async () => {
-			try {
-				const { data } = await api_musicSheetCreationWav2mp3(props.item.filePath);
-				froms.mp3 = data;
-			} catch (error) {
-				message.error("wav转mp3失败");	
-				handleClose();
-			}
-		};
+    const createMusic = async () => {
+      await api_musicSheetCreationSaveMusic({
+        musicSheetCreationId: props.item.id,
+        musicSheetName: props.item.name || "曲谱名称",
+        musicSheetCategoriesId: "",
+        audioType: "MP3",
+        mp3Type: "MP3",
+        xmlFileUrl: props.item.xml,
+        musicSubject: froms.subjectId,
+        showFingering: 1,
+        canEvaluate: 1,
+        notation: 1,
+        playSpeed: props.item?.visualObj?.metaText?.tempo?.bpm || "",
+        background: [
+          {
+            audioFileUrl: froms.mp3,
+            track: "P1",
+          },
+        ],
+        musicImg: froms.musicImg,
+        musicSvg: froms.musicSvg,
+        musicJianSvg: froms.musicJianSvg,
+        extConfigJson: "",
+      });
+    };
+    const wav2mp3 = async () => {
+      try {
+        const { data } = await api_musicSheetCreationWav2mp3(props.item.filePath);
+        froms.mp3 = data;
+      } catch (error) {
+        message.error("wav转mp3失败");
+        handleClose();
+      }
+    };
 
-		const handleClose = () => {
-			model.saveLoading = false;
-			model.saveProgress = 0;
-		};
+    const handleClose = () => {
+      model.saveLoading = false;
+      model.saveProgress = 0;
+    };
 
-		/** 自动生成图片 */
-		const handleAutoProduct = async () => {
-			model.saveProgress = 0;
-			const xml = props.item.xml;
-			const res = await fetch(xml);
-			if (res.status > 299 || res.status < 200) {
-				message.error("xml文件不存在");
-				handleClose();
-				return;
-			}
-			const origin = /(localhost|192)/.test(location.host)
-				? "https://test.lexiaoya.cn"
-				: location.origin;
-			model.productIfameSrc = `${origin}/instrument/#/product-img?xmlUrl=${xml}&productXmlImg=1`;
-			model.productOpen = true;
-			setTimeout(() => {
-				model.saveProgress = 10;
-			}, 800);
-		};
-		const fromRef = ref();
-		const handleUpload = () => {
-			fromRef.value.validate((err: any) => {
-				if (err) {
-					return;
-				}
-				if (!props.item.xml) {
-					message.error("没有生成xml文件");
-					handleClose();
-					return;
-				}
-				if (!props.item.filePath) {
-					message.error("没有生成wav文件");
-					handleClose();
-					return;
-				}
-				model.saveLoading = true;
-				handleAutoProduct();
-			});
-		};
-		const handleSubmit = async () => {
-			await wav2mp3();
-			model.saveProgress = 70;
-			if (!props.show) return;
-			await createMusic();
-			model.saveProgress = 100;
-			emit("success");
-			if (!props.show) return;
-			message.success("上传成功");
-			setTimeout(() => {
-				model.saveLoading = false;
-				emit("update:show", false);
-			}, 300);
-		};
-		return () => (
-			<>
-				<NModal
-					autoFocus={false}
-					show={props.show}
-					onUpdate:show={(val) => {
-						model.productOpen = false;
-						emit("update:show", val);
-					}}
-				>
-					<div class={styles.setbox}>
-						<div class={styles.head}>
-							<div>上传到我的资源</div>
-							<NButton
-								class={styles.close}
-								quaternary
-								circle
-								size="small"
-								onClick={() => {
-									model.productOpen = false;
-									emit("update:show", false);
-								}}
-							>
-								<NIcon component={Close} size={18} />
-							</NButton>
-						</div>
-						<NForm
-							ref={fromRef}
-							model={froms}
-							class={styles.form}
-							labelPlacement="left"
-							showRequireMark={false}
-						>
-							<NFormItem
-								label="可用声部"
-								path="subjectId"
-								rule={{
-									required: true,
-									type: "number",
-									message: "请选择素材可用乐器",
-									trigger: "change",
-								}}
-							>
-								<NSelect
-									to="body"
-									placeholder="请选择素材可用乐器"
-									options={model.subjects}
-									v-model:value={froms.subjectId}
-								></NSelect>
-							</NFormItem>
-							{/* <NFormItem label="是否公开">
+    /** 自动生成图片 */
+    const handleAutoProduct = async () => {
+      model.saveProgress = 0;
+      const xml = props.item.xml;
+      const res = await fetch(xml);
+      if (res.status > 299 || res.status < 200) {
+        message.error("xml文件不存在");
+        handleClose();
+        return;
+      }
+      const origin = /(localhost|192)/.test(location.host) ? "https://test.lexiaoya.cn" : location.origin;
+      model.productIfameSrc = `${origin}/instrument/#/product-img?xmlUrl=${xml}&productXmlImg=1`;
+      model.productOpen = true;
+      setTimeout(() => {
+        model.saveProgress = 10;
+      }, 800);
+    };
+
+    // 生成wav
+    const productWav = async (isUrl = true) => {
+      return new Promise((resolve) => {
+        const midiBuffer = new ABCJS.synth.CreateSynth();
+        midiBuffer
+          .init({
+            visualObj: props.item.visualObj,
+            options: {
+              program: 0,
+              soundFontUrl: "https://oss.dayaedu.com/musicSheet/",
+              // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/", // 默认 FluidR3_GM
+              // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/", // Musyng Kite
+            },
+          })
+          .then(() => {
+            midiBuffer.prime().then(async () => {
+              if (isUrl) {
+                downloadFile(midiBuffer.download(), (props.item.name || "曲谱") + ".wav");
+              } else {
+                const blob = bufferToWave((midiBuffer as any).getAudioBuffer());
+                const wavurl = await api_uploadFile(blob, props.item.id + ".wav");
+                resolve(wavurl);
+              }
+            });
+          });
+      });
+    };
+
+    const fromRef = ref();
+    const handleUpload = () => {
+      fromRef.value.validate(async (err: any) => {
+        if (err) {
+          return;
+        }
+        console.log(props.item, "112");
+        if (!props.item.xml) {
+          message.error("没有生成xml文件");
+          handleClose();
+          return;
+        }
+        // 判断是否有wav文件,如果没有则生成保存
+        if (!props.item.filePath) {
+          const url = await productWav(false);
+          props.item.filePath = url;
+        }
+        if (!props.item.filePath) {
+          message.error("没有生成wav文件");
+          handleClose();
+          return;
+        }
+        model.saveLoading = true;
+        handleAutoProduct();
+      });
+    };
+    const handleSubmit = async () => {
+      await api_musicSheetCreationUpdate({
+        id: props.item.id,
+        subjectId: froms.subjectId,
+        filePath: props.item.filePath,
+      });
+      await wav2mp3();
+      model.saveProgress = 70;
+      if (!props.show) return;
+      await createMusic();
+      model.saveProgress = 100;
+      emit("success");
+      if (!props.show) return;
+      message.success("上传成功");
+      setTimeout(() => {
+        model.saveLoading = false;
+        emit("update:show", false);
+      }, 300);
+    };
+    return () => (
+      <>
+        <NModal
+          autoFocus={false}
+          show={props.show}
+          onUpdate:show={(val) => {
+            model.productOpen = false;
+            emit("update:show", val);
+          }}
+        >
+          <div class={styles.setbox}>
+            <div class={styles.head}>
+              <div>上传到我的资源</div>
+              <NButton
+                class={styles.close}
+                quaternary
+                circle
+                size="small"
+                onClick={() => {
+                  model.productOpen = false;
+                  emit("update:show", false);
+                }}
+              >
+                <NIcon component={Close} size={18} />
+              </NButton>
+            </div>
+            <NForm ref={fromRef} model={froms} class={styles.form} labelPlacement="left" showRequireMark={false}>
+              <NFormItem
+                label="可用声部"
+                path="subjectId"
+                rule={{
+                  required: true,
+                  type: "number",
+                  message: "请选择素材可用乐器",
+                  trigger: "change",
+                }}
+              >
+                <NSelect to="body" placeholder="请选择素材可用乐器" options={model.subjects} v-model:value={froms.subjectId}></NSelect>
+              </NFormItem>
+              {/* <NFormItem label="是否公开">
 								<NSpace class={styles.checkbox} wrapItem={false}>
 									<NButton
 										secondary
@@ -283,29 +301,29 @@ export default defineComponent({
 									</NButton>
 								</NSpace>
 							</NFormItem> */}
-							<NFormItem label="上传进度" style={{ display: model.saveLoading ? "" : "none" }}>
-								<div style={{display: 'flex', width: '100%', height: '46px', alignItems: 'center'}}>
-									<NProgress percentage={model.saveProgress} />
-								</div>
-							</NFormItem>
-						</NForm>
-						<div class={styles.btns}>
-							<NButton
-								onClick={() => {
-									model.productOpen = false;
-									emit("update:show", false);
-								}}
-							>
-								取消
-							</NButton>
-							<NButton type="primary" loading={model.saveLoading} onClick={() => handleUpload()}>
-								确定
-							</NButton>
-						</div>
-					</div>
-				</NModal>
-				{model.productOpen && <iframe class={styles.productIframe} src={model.productIfameSrc}></iframe>}
-			</>
-		);
-	},
+              <NFormItem label="上传进度" style={{ display: model.saveLoading ? "" : "none" }}>
+                <div style={{ display: "flex", width: "100%", height: "46px", alignItems: "center" }}>
+                  <NProgress percentage={model.saveProgress} />
+                </div>
+              </NFormItem>
+            </NForm>
+            <div class={styles.btns}>
+              <NButton
+                onClick={() => {
+                  model.productOpen = false;
+                  emit("update:show", false);
+                }}
+              >
+                取消
+              </NButton>
+              <NButton type="primary" loading={model.saveLoading} onClick={() => handleUpload()}>
+                确定
+              </NButton>
+            </div>
+          </div>
+        </NModal>
+        {model.productOpen && <iframe class={styles.productIframe} src={model.productIfameSrc}></iframe>}
+      </>
+    );
+  },
 });

+ 16 - 2
src/pc/component/upload-to-tasks/index.tsx

@@ -10,6 +10,7 @@ import iconCloseRed from "./images/icon-close-red.png";
 import { NScrollbar, NSpin } from "naive-ui";
 import { eventGlobal, getUploadCatch, saveUploadCatch, uploadState } from "./state";
 import { api_musicalScoreConversionRecordPage, api_musicalScoreConversionRecordRemove } from "../../api";
+import { debounce } from "lodash";
 
 export default defineComponent({
   name: "upload-to-tasks",
@@ -18,6 +19,7 @@ export default defineComponent({
     const state = reactive({
       showUploadList: false,
       timer: null as any,
+      functionTimer: null as any,
     });
 
     /** 是否有等待转换数据 */
@@ -74,6 +76,7 @@ export default defineComponent({
         const temps = data.rows || [];
 
         const result: any = [];
+        let tempSuccess = 0;
         temps.forEach((item: any) => {
           result.push({
             id: item.id,
@@ -82,7 +85,15 @@ export default defineComponent({
             fileName: item.fileName,
             fileUrl: item.fileUrl,
           });
+          if (item.status === "SUCCESS") {
+            tempSuccess++;
+          }
         });
+
+        if (taskStatNums.value.success !== tempSuccess) {
+          eventGlobal.emit("resetList");
+        }
+
         uploadState.uploadList = result;
 
         // 储存
@@ -119,6 +130,9 @@ export default defineComponent({
         }
       }, 5000);
     };
+    const getListDebounce = () => {
+      debounce(() => getRecordList("ids"), 500);
+    };
 
     onMounted(() => {
       // 获取丝缓存数据
@@ -131,14 +145,14 @@ export default defineComponent({
       }
 
       // 上传文件成功之后刷新列表
-      eventGlobal.on("resetUploadTask", () => getRecordList("ids"));
+      eventGlobal.on("resetUploadTask", getListDebounce);
 
       // 开启定时器
       __init();
     });
 
     onUnmounted(() => {
-      eventGlobal.off("resetUploadTask", () => getRecordList());
+      eventGlobal.off("resetUploadTask", getListDebounce);
       clearInterval(state.timer);
     });
     return () => (

+ 4 - 1
src/pc/create/index.tsx

@@ -12,7 +12,7 @@ import { getQuery } from "/src/utils/queryString";
 import { browser } from "/src/utils";
 import UploadToTasks from "../component/upload-to-tasks";
 import UploadFile from "../component/upload-file";
-import { saveUploadCatch, uploadState } from "../component/upload-to-tasks/state";
+import { eventGlobal, saveUploadCatch, uploadState } from "../component/upload-to-tasks/state";
 import { formateAbc, renderMeasures } from "../home/runtime";
 import requestOrigin from "umi-request";
 
@@ -123,10 +123,13 @@ export default defineComponent({
       window.addEventListener("message", (params?: any) => {
         messageEvent(params);
       });
+
+      eventGlobal.on("resetList", handleReset);
     });
 
     onUnmounted(() => {
       window.removeEventListener("message", messageEvent);
+      eventGlobal.off("resetList", handleReset);
     });
     const handleOpenNotaion = (item: any) => {
       window.parent.postMessage(