Sfoglia il codice sorgente

Merge branch 'iteration-temp-http' of http://git.dayaedu.com/huangqiyong/classroom into dev

黄琪勇 7 mesi fa
parent
commit
c2dfac6fc7

+ 5 - 5
public/osmd/index.html

@@ -117,11 +117,11 @@
         .then(
           function () {
             // 是否合并显示
-            if (!isComberRender) {
-              for (let i = 0; i < osmd.Sheet.Instruments.length; i++) {
-                osmd.Sheet.Instruments[i].Visible = i === partIndex;
-              }
-            }
+            // if (!isComberRender) {
+            //   for (let i = 0; i < osmd.Sheet.Instruments.length; i++) {
+            //     osmd.Sheet.Instruments[i].Visible = i === partIndex;
+            //   }
+            // }
 
             osmd.zoom = .5
             render();

+ 1 - 0
public/pdf/web/viewer.html

@@ -522,6 +522,7 @@ See https://github.com/adobe-type-tools/cmap-resources
 
   <script src="viewer.js"></script>
   <script>
+    localStorage.removeItem("pdfjs.history")
    function getQueryVariable(variable) {
       if (window.location.hash.indexOf("?") < 0) {
         return null;

+ 81 - 83
src/views/cloudPractice/cloudPractice.tsx

@@ -1,11 +1,10 @@
 import { computed, defineComponent, nextTick, onMounted, reactive, ref, shallowRef } from "vue"
 import styles from "./index.module.scss"
 import NavContainer from "@/businessComponents/navContainer"
-import { ElEmpty, ElScrollbar } from "element-plus"
+import { ElEmpty, ElMessage, ElScrollbar } from "element-plus"
 import Dictionary from "@/components/dictionary"
 import MyInput from "@/components/myInput"
 import { NImage, NPopselect, NSpin, NTooltip } from "naive-ui"
-// import PlayLoading from "./component/play-loading"
 import PlayItem from "./component/play-item"
 import icon_default from "../../img/cloudPractice/icon_default.png"
 import iconBtnPause from "../../img/cloudPractice/icon-btn-pause.png"
@@ -26,8 +25,6 @@ import {
    queryTree_klx,
    selectCondition_klx
 } from "@/api/cloudPractice.api"
-// import { getToken } from "@/libs/auth"
-// import { URL_TEACH_GYM } from "@/config"
 import axios from "axios"
 import { getInstrumentName } from "@/libs/instruments"
 import { formatXML, getCustomInfo, onlyVisible } from "./instrument"
@@ -36,8 +33,8 @@ import userStore from "@/store/modules/user"
 import PlayLoading from "./component/play-loading"
 import PracticeForm from "@/businessComponents/practiceForm"
 import { saveAs } from "file-saver"
-// import JSZip, { file } from "jszip"
-// import { svgtopng } from "./formatSvgToImg"
+import JSZip from "jszip"
+import { svgtoblob } from "./formatSvgToImg"
 
 export default defineComponent({
    name: "cloudPractice",
@@ -107,7 +104,6 @@ export default defineComponent({
                isComberRender: item?.isScoreRender,
                musicPdfUrl: item?.musicPdfUrl // 独奏使用PDF
             }
-            console.log(tempList, "tempList")
          } else if (userStoreHook.roles === "GYT") {
             tempList = {
                id: list?.id,
@@ -133,7 +129,6 @@ export default defineComponent({
                isComberRender: false,
                musicPdfUrl: list?.musicPdfUrl
             }
-            console.log(tempList, "tempList")
          }
          return tempList
       })
@@ -155,6 +150,7 @@ export default defineComponent({
          }
       })
 
+      const downloadStatus = ref(false)
       const loading = ref(false)
       const staffLoading = ref(false)
       const storeData = shallowRef<any[]>([])
@@ -222,7 +218,6 @@ export default defineComponent({
                   state.list = []
                   state.reshing = false
                }
-               console.log(res, result, "result")
                if (Array.isArray(result.rows)) {
                   state.list = [...state.list, ...result.rows]
                   state.finshed = state.page >= result.totalPage
@@ -707,8 +702,6 @@ export default defineComponent({
       const handleGetList = async () => {
          if (loading.value) return
          state.listActive = 0
-         state.selectedPartIndex = 0
-         state.partXmlIndex = 0
          state.showPlayer = false
          state.playState = "pause"
          state.partNames = []
@@ -716,6 +709,7 @@ export default defineComponent({
          state.selectedPartName = ""
          state.selectedTrack = ""
          state.selectedPartIndex = 0
+         // state.musicPdfUrl = ""
          state.partXmlIndex = 0
          document.querySelector(".musicList-container")?.scroll(0, 0)
          state.page = 1
@@ -746,7 +740,6 @@ export default defineComponent({
             } else if (userStoreHook.roles === "KLX") {
                musicPdfUrl = item.musicPdfUrl
             }
-            console.log(item, "item")
             return {
                label: item.track + (instrumentName ? `(${instrumentName})` : ""),
                instrumentName: instrumentName,
@@ -761,7 +754,11 @@ export default defineComponent({
          state.selectedPartName = defaultShowStaff?.instrumentName
          state.selectedTrack = defaultShowStaff?.track
          state.partXmlIndex = defaultShowStaff?.xmlIndex
-         state.musicPdfUrl = defaultShowStaff?.musicPdfUrl || ""
+         if (row.isComberRender) {
+            state.musicPdfUrl = row?.musicPdfUrl || ""
+         } else {
+            state.musicPdfUrl = defaultShowStaff?.musicPdfUrl || ""
+         }
       }
 
       const getPartNames = async (xmlUrl: string) => {
@@ -805,7 +802,6 @@ export default defineComponent({
             if (activeItem.value.isComberRender) {
                iframeRef.contentWindow.renderXml(xml, state.partXmlIndex, activeItem.value.isComberRender)
             } else {
-               console.log(state.partXmlIndex, " state.partXmlIndex")
                const currentXml = onlyVisible(xml, state.partXmlIndex)
                iframeRef.contentWindow.renderXml(currentXml, 0, activeItem.value.isComberRender)
             }
@@ -815,7 +811,7 @@ export default defineComponent({
       const renderStaff = async () => {
          try {
             if (state.musicPdfUrl) {
-               state.iframeSrc = "/pdf/web/viewer.html?file=" + encodeURIComponent(state.musicPdfUrl)
+               state.iframeSrc = "/pdf/web/viewer.html?file=" + encodeURIComponent(state.musicPdfUrl) + "&t=" + Date.now()
                // https://cdn.oss.dayaedu.com/daya202409/UOFW4q5.pdf
                // https://cdn.oss.dayaedu.com/daya202409/UOFVK2A.pdf
                // https://cdn.oss.dayaedu.com/daya202409/UODQffO.pdf
@@ -858,76 +854,56 @@ export default defineComponent({
          }
       }
 
-      // // 获取文件blob格式
-      // const getFileBlob = (url: string) => {
-      //    return new Promise((resolve, reject) => {
-      //       const request = new XMLHttpRequest()
-      //       request.open("GET", url, true)
-      //       request.responseType = "blob"
-      //       request.onload = (res: any) => {
-      //          if (res.target.status == 200) {
-      //             resolve(res.target.response)
-      //          } else {
-      //             reject(res)
-      //          }
-      //       }
-      //       request.send()
-      //    })
-      // }
-
       // // 多个文件下载
-      // const downLoadMultiFile = (files: any, filesName: string) => {
-      //    const zip = new JSZip()
-      //    const result = []
-      //    for (const i in files) {
-      //       const promise = getFileBlob(files[i].url).then((res: any) => {
-      //          zip.file(files[i].name, res, { binary: true })
-      //       })
-      //       result.push(promise)
-      //    }
-      //    Promise.all(result)
-      //       .then(() => {
-      //          zip.generateAsync({ type: "blob" }).then(res => {
-      //             saveAs(res, filesName ? filesName + ".zip" : `文件夹${Date.now()}.zip`)
-      //          })
-      //       })
-      //       .catch(() => {
-      //          //  message.error('下载失败');
-      //       })
-
-      //    // downloadStatus.value = false;
-      // }
+      const downLoadMultiFile = (files: any, filesName: string) => {
+         const zip = new JSZip()
+         const result = []
+         console.log(files)
+         for (const i in files) {
+            zip.file(files[i].name, files[i].url, { binary: true })
+         }
+         zip.generateAsync({ type: "blob" }).then(res => {
+            saveAs(res, filesName ? filesName + ".zip" : `文件夹${Date.now()}.zip`)
+         })
+         downloadStatus.value = false
+      }
 
       const showLoading = async (e: any) => {
          if (e.data?.api === "musicStaffRender") {
-            // try {
-            //    const osmdImg = e.data.osmdImg
-            //    const imgs = []
-            //    for (let i = 0; i < osmdImg.length; i++) {
-            //       const img = await svgtopng(osmdImg[i].img, osmdImg[i].width, osmdImg[i].height)
-            //       imgs.push({
-            //          url: img,
-            //          name: i + "-" + Date.now()
-            //       })
-            //    }
-            //    state.imgs = imgs
-            // } catch (e) {
-            //    // console.log(e);
-            // }
+            const musicName =
+               activeItem.value.name +
+               ((activeItem.value.musicSheetType === "CONCERT" && state.selectedPartName) || state.selectedTrack
+                  ? `(${state.selectedPartName || state.selectedTrack})`
+                  : "")
+            console.log(musicName, "musicName")
+            try {
+               const osmdImg = e.data.osmdImg
+               const imgs = []
+               for (let i = 0; i < osmdImg.length; i++) {
+                  const img = await svgtoblob(osmdImg[i].img, osmdImg[i].width, osmdImg[i].height, musicName)
+                  imgs.push({
+                     url: img,
+                     name: i + 1 + ".png"
+                  })
+               }
+               state.imgs = imgs
+            } catch (e) {
+               // console.log(e);
+            }
             staffLoading.value = e.data.loading
+            loading.value = e.data.loading
          }
       }
 
       const searchContent = async () => {
-         const status = state.musicPdfUrl ? true : false
          await toDetail()
          if (activeItem.value?.id) {
             if (state.musicPdfUrl) {
                staffLoading.value = true
                renderStaff()
             } else {
-               if (status) {
-                  state.musicPdfUrl = ""
+               // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
+               if (state.iframeSrc.indexOf("pdf/web") !== -1) {
                   renderStaff()
                } else {
                   resetRender()
@@ -938,23 +914,27 @@ export default defineComponent({
 
       /** 下载图片 */
       const onDownload = () => {
+         if (downloadStatus.value || staffLoading.value) return
          const musicName =
             activeItem.value.name +
-            (activeItem.value.musicSheetType === "CONCERT" &&
-               (state.selectedPartName || state.selectedTrack ? `(${state.selectedPartName || state.selectedTrack})` : ""))
-
+            ((activeItem.value.musicSheetType === "CONCERT" && state.selectedPartName) || state.selectedTrack
+               ? `(${state.selectedPartName || state.selectedTrack})`
+               : "")
+         downloadStatus.value = true
          if (state.musicPdfUrl) {
             // 发起Fetch请求
             fetch(state.musicPdfUrl)
                .then(response => response.blob())
                .then(blob => {
                   saveAs(blob, musicName)
+                  downloadStatus.value = false
                })
                .catch(() => {
-                  //   message.error('下载失败');
+                  ElMessage.error("下载失败")
+                  downloadStatus.value = false
                })
          } else {
-            // downLoadMultiFile(state.imgs, musicName)
+            downLoadMultiFile(state.imgs, musicName)
          }
       }
 
@@ -1241,9 +1221,20 @@ export default defineComponent({
                   />
 
                   <div class={styles.rightBtns} style={{ display: activeItem.value.id ? "" : "none" }}>
-                     <div style={{ display: state.musicPdfUrl ? "" : "none" }}>
+                     <div
+                     // style={{ display: state.musicPdfUrl ? "" : "none" }}
+                     >
                         <NTooltip showArrow={false}>
-                           {{ trigger: () => <img onClick={onDownload} class={styles.transBtn} src={iconDownload as any} />, default: "下载曲谱" }}
+                           {{
+                              trigger: () => (
+                                 <img
+                                    onClick={onDownload}
+                                    class={[styles.transBtn, (downloadStatus.value || staffLoading.value) && styles.disableBtn]}
+                                    src={iconDownload as any}
+                                 />
+                              ),
+                              default: "下载曲谱"
+                           }}
                         </NTooltip>
                      </div>
                      <div style={{ display: activeItem.value.musicSheetType === "CONCERT" ? "" : "none" }}>
@@ -1254,27 +1245,34 @@ export default defineComponent({
                            v-model:value={state.selectedPartIndex}
                            scrollable
                            onUpdate:value={async (value: any) => {
-                              console.log(value, "value")
                               const item = partColumns.value.find((item: any) => item.value === value)
                               state.selectedPartIndex = value
                               state.selectedPartName = item.instrumentName
                               state.selectedTrack = item.track
                               state.partXmlIndex = item.xmlIndex
                               nextTick(() => {
-                                 if (item && item.musicPdfUrl) {
+                                 let tempPdf = ""
+                                 if (activeItem.value?.isComberRender) {
+                                    if (activeItem.value?.musicPdfUrl) {
+                                       tempPdf = activeItem.value?.musicPdfUrl
+                                    }
+                                 } else {
+                                    tempPdf = item.musicPdfUrl
+                                 }
+                                 if (tempPdf) {
+                                    state.musicPdfUrl = tempPdf
                                     staffLoading.value = true
-                                    state.musicPdfUrl = item.musicPdfUrl
                                     renderStaff()
                                  } else {
+                                    state.musicPdfUrl = ""
                                     loading.value = true
-                                    if (state.musicPdfUrl) {
-                                       state.musicPdfUrl = ""
+                                    // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
+                                    if (state.iframeSrc.indexOf("pdf/web") !== -1) {
                                        renderStaff()
                                     } else {
                                        resetRender()
                                     }
                                  }
-                                 console.log(value, "value", item)
                               })
                            }}
                            class={["PopSelect", "PopSelectPart"]}

+ 107 - 8
src/views/cloudPractice/formatSvgToImg.ts

@@ -29,7 +29,7 @@ if (!window.OffscreenCanvas) {
 }
 
 const preset: any = presets.offscreen()
-const blobToBase64 = (blob: any) => {
+export const blobToBase64 = (blob: any) => {
    return new Promise(resolve => {
       const reader = new FileReader()
       reader.onloadend = () => resolve(reader.result)
@@ -37,12 +37,32 @@ const blobToBase64 = (blob: any) => {
    })
 }
 
-export const svgtopng = async (svg: any, width: any, height: any) => {
-   // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-   // @ts-ignore
-   const canvas = new OffscreenCanvas(width, height)
+export const addMusicTitle = (canvas: any, title: any) => {
+   canvas.getContext("2d")
+   const water = document.createElement("canvas")
+
+   // 小水印画布大小
+   water.width = canvas.width
+   water.height = canvas.height + 120
+   const waterCtx = water.getContext("2d") as CanvasRenderingContext2D
+   waterCtx.font = `66pt Calibri`
+   waterCtx.fillStyle = "#000"
+   waterCtx.textAlign = "center"
+   waterCtx.drawImage(canvas, 0, 40)
+   waterCtx.fillText(title, canvas.width / 2, 160)
+   return water
+}
+
+let canvas = null as any
+export const svgtoblob = async (svg: any, width: any, height: any, name: string) => {
+   if (!canvas) {
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-ignore
+      canvas = new OffscreenCanvas(width, height)
+   }
+
    const ctx = canvas.getContext("2d")!
-   const v = await Canvg.fromString(ctx!, svg, preset)
+   let v: any = await Canvg.fromString(ctx!, svg, preset)
 
    /**
     * Resize SVG to fit in given size.
@@ -54,7 +74,86 @@ export const svgtopng = async (svg: any, width: any, height: any) => {
 
    // Render only first frame, ignoring animations and mouse.
    await v.start()
-   const blob = await canvas.convertToBlob()
-   const base64 = await blobToBase64(blob)
+   // const blob = await canvas.convertToBlob()
+   // const base64 = await blobToBase64(blob)
+   const base64 = await canvasAddPadding(canvas, name)
+   ctx.clearRect(0, 0, canvas.width, canvas.height)
+   await v.stop()
+   v = null
    return base64
 }
+
+const convertToBlob = (canvas: any, type = "image/png") => {
+   return new Promise((resolve, reject) => {
+      canvas.toBlob((blob: Blob) => {
+         if (blob) {
+            resolve(blob)
+         } else {
+            reject(new Error("转换失败"))
+         }
+      }, type)
+   })
+}
+
+const canvasAddPadding = async (sourceCanvas: any, name: string) => {
+   const targetCanvas = document.createElement("canvas")
+   targetCanvas.width = sourceCanvas.width + 400
+   targetCanvas.height = sourceCanvas.height + 200
+
+   // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
+   const targetContext = targetCanvas.getContext("2d") as CanvasRenderingContext2D
+
+   // const sourceContext = sourceCanvas.getContext("2d") as CanvasRenderingContext2D
+
+   // 从源canvas中获取图像数据
+   // const imageData = sourceContext.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height)
+
+   // 清空目标canvas
+   targetContext.clearRect(0, 0, targetCanvas.width, targetCanvas.height)
+
+   // 将图像数据绘制到目标canvas上,并添加边距
+   // targetContext.putImageData(imageData, 200, 100)
+   targetContext.fillStyle = "#fff"
+   targetContext.fillRect(0, 0, targetCanvas.width, targetCanvas.height)
+   // targetCanvas = await addMusicTitle(targetCanvas, name)
+
+   // 小水印画布大小
+   // const waterCtx = water.getContext("2d") as CanvasRenderingContext2D
+   targetContext.font = `66pt Calibri`
+   targetContext.fillStyle = "#000"
+   targetContext.textAlign = "center"
+   targetContext.drawImage(sourceCanvas, 200, 240)
+   targetContext.fillText(name, targetCanvas.width / 2, 200)
+
+   const blob = await convertToBlob(targetCanvas)
+   // const base64 = await blobToBase64(blob)
+   targetContext.clearRect(0, 0, targetCanvas.width, targetCanvas.height)
+   return blob
+}
+
+// // 获取文件blob格式
+export const imgToCanvas = async (url: string) => {
+   const img = document.createElement("img")
+   img.setAttribute("crossOrigin", "anonymous")
+   // 为了处理base64 和 连接加载不同的
+   if (url && typeof url == "string" && url.includes("data:image")) {
+      img.src = url
+   } else {
+      img.src = url + `?t=${+new Date()}`
+   }
+
+   // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
+   await new Promise(resolve => (img.onload = resolve))
+   // 创建canvas DOM元素,并设置其宽高和图片一样
+   const canvas = document.createElement("canvas")
+   canvas.width = img.width
+   canvas.height = img.height
+   // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
+   const ctx = canvas.getContext("2d") as CanvasRenderingContext2D
+
+   ctx.fillStyle = "rgb(255, 255, 255)"
+   ctx.fillStyle = "#fff"
+   ctx.fillRect(0, 0, img.width, img.height)
+   ctx.drawImage(img, 0, 0)
+   return canvas
+}

+ 4 - 0
src/views/cloudPractice/index.module.scss

@@ -508,6 +508,10 @@
             opacity: 0.7;
          }
       }
+      .disableBtn {
+         opacity: 0.7;
+         cursor: not-allowed
+      }
    }
 
    // .popSelect {