123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- import { defineComponent, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
- 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,
- };
- });
- };
- 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 () => {
- 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 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 subjectCode = props.item.subjectCode || "acoustic_grand_piano";
- const _instruments = ABCJS.synth.instrumentIndexToName.indexOf(subjectCode);
- const program = _instruments > -1 ? _instruments : 0;
- const midiBuffer = new ABCJS.synth.CreateSynth();
- midiBuffer
- .init({
- visualObj: props.item.visualObj,
- options: {
- program,
- 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 fileName = +new Date() + Math.ceil(Math.random() * 1000);
- const wavurl = await api_uploadFile(blob, fileName + ".wav");
- resolve(wavurl);
- }
- });
- });
- });
- };
- const fromRef = ref();
- const handleUpload = () => {
- fromRef.value.validate(async (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 () => {
- // 判断是否有wav文件,如果没有则生成保存
- // if (!props.item.filePath) {
- const url = await productWav(false);
- props.item.filePath = url;
- // }
- 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}
- unstableShowMask={false}
- maskClosable={false}
- 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" disabled={model.saveLoading} placeholder="请选择素材可用乐器" options={model.subjects} v-model:value={froms.subjectId}></NSelect>
- </NFormItem>
- {/* <NFormItem label="是否公开">
- <NSpace class={styles.checkbox} wrapItem={false}>
- <NButton
- secondary
- bordered={false}
- type={froms.isPublic === 1 ? "primary" : "default"}
- onClick={() => (froms.isPublic = 1)}
- >
- 公开
- </NButton>
- <NButton
- secondary
- bordered={false}
- type={froms.isPublic === 0 ? "primary" : "default"}
- onClick={() => (froms.isPublic = 0)}
- >
- 不公开
- </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;
- handleClose();
- 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>}
- </>
- );
- },
- });
|