index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import { defineComponent, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
  2. import ABCJS, { AbcElem, AbcVisualParams, ClickListenerAnalysis, ClickListenerDrag, NoteTimingEvent, SynthObjectController } from "abcjs";
  3. import { api_musicSheetCreationWav2mp3, api_musicSheetCreationSaveMusic, api_subjectList, api_musicSheetCreationUpdate } from "../../api";
  4. import { NButton, NForm, NFormItem, NIcon, NModal, NProgress, NSelect, NSpace, useMessage } from "naive-ui";
  5. import styles from "./index.module.less";
  6. import { Close } from "@vicons/ionicons5";
  7. import { SelectMixedOption } from "naive-ui/es/select/src/interface";
  8. import { api_uploadFile } from "/src/utils/uploadFile";
  9. import { bufferToWave } from "/src/helpers/parseABC";
  10. import { decodeUrl, downloadFile } from "/src/utils";
  11. import { renderMeasures } from "../../home/runtime";
  12. import cleanDeep from "clean-deep";
  13. export default defineComponent({
  14. name: "UploadToResources",
  15. props: {
  16. show: {
  17. type: Boolean,
  18. default: false,
  19. },
  20. item: {
  21. type: Object,
  22. default: () => ({}),
  23. },
  24. },
  25. emits: ["update:show", "success"],
  26. setup(props, { emit }) {
  27. const message = useMessage();
  28. const model = reactive({
  29. subjects: [] as SelectMixedOption[],
  30. saveLoading: false,
  31. saveProgress: 0,
  32. productOpen: false,
  33. productIfameSrc: "",
  34. });
  35. const froms = reactive({
  36. subjectId: null,
  37. isPublic: 0,
  38. mp3: "",
  39. musicImg: "",
  40. musicSvg: "",
  41. musicJianSvg: "",
  42. });
  43. const getSubjects = async () => {
  44. const { data } = await api_subjectList();
  45. model.subjects = data.map((item: any) => {
  46. return {
  47. label: item.name,
  48. value: item.id,
  49. };
  50. });
  51. };
  52. const handleProductResult = (res: MessageEvent) => {
  53. const data = res.data;
  54. if (data?.api === "webApi_renderSvg") {
  55. let imgs: any = [];
  56. try {
  57. imgs = JSON.parse(data.product);
  58. } catch (error) {
  59. console.log("🚀 ~ error:", error);
  60. }
  61. imgs = imgs.filter((item: any) => item.base64);
  62. if (imgs.length === 3) {
  63. handleUploadImg(imgs);
  64. }
  65. console.log("🚀 ~ 上传之前", [...imgs]);
  66. }
  67. };
  68. const handleUploadImg = async (imgs: any[]) => {
  69. if (!props.show) return;
  70. for (let i = 0; i < imgs.length; i++) {
  71. const fileName = `${Date.now()}p${i}.png`;
  72. const file = dataURLtoFile(imgs[i].base64, fileName);
  73. imgs[i].url = await api_uploadFile(file, fileName, () => {});
  74. model.saveProgress = (i + 1) * 20;
  75. }
  76. froms.musicImg = imgs[0]?.url || "";
  77. froms.musicSvg = imgs[1]?.url || "";
  78. froms.musicJianSvg = imgs[2]?.url || "";
  79. model.productOpen = false;
  80. imgs = [];
  81. if (!props.show) return;
  82. handleSubmit();
  83. };
  84. /** base64转file */
  85. const dataURLtoFile = (dataurl: string, filename: string) => {
  86. let arr = dataurl.split(",") || [],
  87. mime = arr[0].match(/:(.*?);/)?.[1],
  88. bstr = atob(arr[1]),
  89. n = bstr.length,
  90. u8arr = new Uint8Array(n);
  91. while (n--) {
  92. u8arr[n] = bstr.charCodeAt(n);
  93. }
  94. return new File([u8arr], filename, { type: mime });
  95. };
  96. onMounted(() => {
  97. getSubjects();
  98. window.addEventListener("message", handleProductResult);
  99. });
  100. onUnmounted(() => {
  101. window.removeEventListener("message", handleProductResult);
  102. });
  103. watch(
  104. () => props.item,
  105. () => {
  106. // console.log(props.item, model.subjects);
  107. const subjectId = model.subjects.length > 0 ? model.subjects[0].value : null;
  108. froms.subjectId = props.item.subjectId ?? subjectId;
  109. }
  110. );
  111. const createMusic = async () => {
  112. await api_musicSheetCreationSaveMusic({
  113. musicSheetCreationId: props.item.id,
  114. musicSheetName: props.item.name || "曲谱名称",
  115. musicSheetCategoriesId: "",
  116. audioType: "MP3",
  117. mp3Type: "MP3",
  118. xmlFileUrl: props.item.xml,
  119. musicSubject: froms.subjectId,
  120. showFingering: 1,
  121. canEvaluate: 1,
  122. notation: 1,
  123. playSpeed: props.item?.visualObj?.metaText?.tempo?.bpm || "",
  124. background: [
  125. {
  126. audioFileUrl: froms.mp3,
  127. track: "P1",
  128. },
  129. ],
  130. musicImg: froms.musicImg,
  131. musicSvg: froms.musicSvg,
  132. musicJianSvg: froms.musicJianSvg,
  133. extConfigJson: "",
  134. });
  135. };
  136. const wav2mp3 = async () => {
  137. try {
  138. const { data } = await api_musicSheetCreationWav2mp3(props.item.filePath);
  139. froms.mp3 = data;
  140. } catch (error) {
  141. message.error("wav转mp3失败");
  142. handleClose();
  143. }
  144. };
  145. const handleClose = () => {
  146. model.saveLoading = false;
  147. model.saveProgress = 0;
  148. };
  149. /** 自动生成图片 */
  150. const handleAutoProduct = async () => {
  151. model.saveProgress = 0;
  152. const xml = props.item.xml;
  153. const res = await fetch(xml);
  154. if (res.status > 299 || res.status < 200) {
  155. message.error("xml文件不存在");
  156. handleClose();
  157. return;
  158. }
  159. const origin = /(localhost|192)/.test(location.host) ? "https://test.lexiaoya.cn" : location.origin;
  160. model.productIfameSrc = `${origin}/instrument/#/product-img?xmlUrl=${xml}&productXmlImg=1`;
  161. model.productOpen = true;
  162. setTimeout(() => {
  163. model.saveProgress = 10;
  164. }, 800);
  165. };
  166. // 生成wav
  167. const productWav = async (isUrl = true) => {
  168. return new Promise((resolve) => {
  169. const subjectCode = props.item.subjectCode || "acoustic_grand_piano";
  170. const _instruments = ABCJS.synth.instrumentIndexToName.indexOf(subjectCode);
  171. const program = _instruments > -1 ? _instruments : 0;
  172. const midiBuffer = new ABCJS.synth.CreateSynth();
  173. midiBuffer
  174. .init({
  175. visualObj: props.item.visualObj,
  176. options: {
  177. program,
  178. soundFontUrl: "https://oss.dayaedu.com/musicSheet/",
  179. // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/", // 默认 FluidR3_GM
  180. // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/", // Musyng Kite
  181. },
  182. })
  183. .then(() => {
  184. midiBuffer.prime().then(async () => {
  185. if (isUrl) {
  186. downloadFile(midiBuffer.download(), (props.item.name || "曲谱") + ".wav");
  187. } else {
  188. const blob = bufferToWave((midiBuffer as any).getAudioBuffer());
  189. const fileName = +new Date() + Math.ceil(Math.random() * 1000);
  190. const wavurl = await api_uploadFile(blob, fileName + ".wav");
  191. resolve(wavurl);
  192. }
  193. });
  194. });
  195. });
  196. };
  197. const fromRef = ref();
  198. const handleUpload = () => {
  199. fromRef.value.validate(async (err: any) => {
  200. if (err) {
  201. return;
  202. }
  203. if (!props.item.xml) {
  204. message.error("没有生成xml文件");
  205. handleClose();
  206. return;
  207. }
  208. // if (!props.item.filePath) {
  209. // message.error("没有生成wav文件");
  210. // handleClose();
  211. // return;
  212. // }
  213. model.saveLoading = true;
  214. handleAutoProduct();
  215. });
  216. };
  217. const handleSubmit = async () => {
  218. // 判断是否有wav文件,如果没有则生成保存
  219. // if (!props.item.filePath) {
  220. const url = await productWav(false);
  221. props.item.filePath = url;
  222. // }
  223. await api_musicSheetCreationUpdate({
  224. id: props.item.id,
  225. subjectId: froms.subjectId,
  226. // filePath: props.item.filePath,
  227. });
  228. await wav2mp3();
  229. model.saveProgress = 70;
  230. if (!props.show) return;
  231. await createMusic();
  232. model.saveProgress = 100;
  233. emit("success");
  234. if (!props.show) return;
  235. message.success("上传成功");
  236. setTimeout(() => {
  237. model.saveLoading = false;
  238. emit("update:show", false);
  239. }, 300);
  240. };
  241. return () => (
  242. <>
  243. <NModal
  244. autoFocus={false}
  245. show={props.show}
  246. unstableShowMask={false}
  247. maskClosable={false}
  248. onUpdate:show={(val) => {
  249. model.productOpen = false;
  250. emit("update:show", val);
  251. }}
  252. >
  253. <div class={styles.setbox}>
  254. <div class={styles.head}>
  255. <div>上传到我的资源</div>
  256. <NButton
  257. class={styles.close}
  258. quaternary
  259. circle
  260. size="small"
  261. onClick={() => {
  262. model.productOpen = false;
  263. emit("update:show", false);
  264. }}
  265. >
  266. <NIcon component={Close} size={18} />
  267. </NButton>
  268. </div>
  269. <NForm ref={fromRef} model={froms} class={styles.form} labelPlacement="left" showRequireMark={false}>
  270. <NFormItem
  271. label="可用声部"
  272. path="subjectId"
  273. rule={{
  274. required: true,
  275. type: "number",
  276. message: "请选择素材可用乐器",
  277. trigger: "change",
  278. }}
  279. >
  280. <NSelect to="body" disabled={model.saveLoading} placeholder="请选择素材可用乐器" options={model.subjects} v-model:value={froms.subjectId}></NSelect>
  281. </NFormItem>
  282. {/* <NFormItem label="是否公开">
  283. <NSpace class={styles.checkbox} wrapItem={false}>
  284. <NButton
  285. secondary
  286. bordered={false}
  287. type={froms.isPublic === 1 ? "primary" : "default"}
  288. onClick={() => (froms.isPublic = 1)}
  289. >
  290. 公开
  291. </NButton>
  292. <NButton
  293. secondary
  294. bordered={false}
  295. type={froms.isPublic === 0 ? "primary" : "default"}
  296. onClick={() => (froms.isPublic = 0)}
  297. >
  298. 不公开
  299. </NButton>
  300. </NSpace>
  301. </NFormItem> */}
  302. <NFormItem label="上传进度" style={{ display: model.saveLoading ? "" : "none" }}>
  303. <div style={{ display: "flex", width: "100%", height: "46px", alignItems: "center" }}>
  304. <NProgress percentage={model.saveProgress} />
  305. </div>
  306. </NFormItem>
  307. </NForm>
  308. <div class={styles.btns}>
  309. <NButton
  310. onClick={() => {
  311. model.productOpen = false;
  312. handleClose();
  313. emit("update:show", false);
  314. }}
  315. >
  316. 取消
  317. </NButton>
  318. <NButton type="primary" loading={model.saveLoading} onClick={() => handleUpload()}>
  319. 确定
  320. </NButton>
  321. </div>
  322. </div>
  323. </NModal>
  324. {model.productOpen && <iframe class={styles.productIframe} src={model.productIfameSrc}></iframe>}
  325. </>
  326. );
  327. },
  328. });