index.tsx 12 KB

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