Browse Source

评卷员端图片预加载

刘洋 1 year ago
parent
commit
a81de527f1
2 changed files with 221 additions and 3 deletions
  1. 15 3
      src/modules/marking/mark/index.vue
  2. 206 0
      src/utils/usePreloadImageToTask.ts

+ 15 - 3
src/modules/marking/mark/index.vue

@@ -34,7 +34,6 @@
       </div>
       <div
         v-else-if="!!currentTask"
-        v-loading="imgLoading"
         class="flex flex-1 direction-column radius-base full fill-blank mark-content"
         :style="{ 'background-color': backgroundColor }"
       >
@@ -57,7 +56,7 @@
         <div ref="imgWrap" class="img-wrap scroll-auto flex-1 p-base" :class="{ 'text-center': center }">
           <img
             ref="paperImg"
-            :src="dataUrl"
+            :src="currentTask.dataUrl"
             alt=""
             class="paper-img"
             :style="{ 'background-color': frontColor, maxWidth: '100%' }"
@@ -167,6 +166,7 @@ import ReMarkingStatus from '@/assets/images/status-remarking.png'
 import SampleAStatus from '@/assets/images/status-sample-a.png'
 import SampleBStatus from '@/assets/images/status-sample-b.png'
 import RemarkBox from './remark-box.vue'
+import { usePreloadImageToTask } from '@/utils/usePreloadImageToTask'
 import type { SetImgBgOption } from '@/hooks/useSetImgBg'
 import type { ExtractApiResponse } from '@/api/api'
 import type { MarkHeaderInstance } from 'global-type'
@@ -217,7 +217,8 @@ type TaskInfoType = FormalTaskType | HistoryTaskType
 // let currentTaskPool: ExtractApiResponse<'getMarkingTask'> = []
 let currentTaskPool: any = []
 
-const currentTask = ref<TaskInfoType>()
+// const currentTask = ref<TaskInfoType>()
+const currentTask = ref<any>()
 
 type excludeOperationTypes = InstanceType<typeof MarkHeader>['$props']['excludeOperations']
 
@@ -239,6 +240,7 @@ const historyTaskChange = (task: any) => {
   console.log('saveTargetTask:', saveTargetTask)
   if (currentTaskType.value != 'remarking' && saveTargetTask) {
     currentTaskPool.unshift(saveTargetTask)
+    handleTaskPool()
   }
   currentTask.value = task
   currentTaskType.value = 'remarking'
@@ -300,6 +302,7 @@ const refreshTaskPool = (force = false, isRefresh = false, hasLimit = false) =>
             } else {
               currentTaskPool.push(...result)
             }
+            handleTaskPool()
           }
           if (currentTaskPool?.length && !currentTask.value) {
             setCurrentTask()
@@ -497,6 +500,7 @@ const clearTaskView = () => {
   currentTaskPool = currentTaskPool.filter((item: any) => {
     return item.taskType === 'FORCE'
   })
+  handleTaskPool()
   currentTask.value = undefined
 }
 const checkMarkStatus = async (): Promise<boolean> => {
@@ -664,6 +668,14 @@ const imgLoaded = () => {
   // previewLeft.value = paperImg.value.width - 40 + 'px'
   showPreviewBtn.value = true
 }
+
+const handleTaskPool = () => {
+  currentTaskPool.forEach((task: any) => {
+    if (!task.dataUrl) {
+      usePreloadImageToTask(imgOption, frontColor, setFrontColor, task)
+    }
+  })
+}
 </script>
 
 <style scoped lang="scss">

+ 206 - 0
src/utils/usePreloadImageToTask.ts

@@ -0,0 +1,206 @@
+import { ref, onScopeDispose, watch, nextTick, unref, computed, watchEffect } from 'vue'
+import type { Ref } from 'vue'
+import { isDom } from '@/utils/common'
+import TintImageWorker from '@/utils/image.worker?worker'
+import useMainStore from '@/store/main'
+import { useRoute } from 'vue-router'
+import analyze from 'rgbaster'
+export type RGBA = number[]
+
+export type Point = [number, number]
+export interface SetImgBgOption {
+  image?: string | HTMLImageElement | null
+  basePoint?: Point[]
+  distance?: number
+  enableSharpen?: boolean
+  rotate?: number
+  scale?: number
+}
+
+interface InitOption {
+  frontColor?: Ref<string>
+  setFrontColor?: any
+}
+
+function getRgba(canvas: any, that: any) {
+  const imgWidth = that.width
+  const imgHeight = that.height
+
+  canvas.width = imgWidth
+  canvas.height = imgHeight
+
+  const context = canvas.getContext('2d')
+
+  context.drawImage(that, 0, 0, imgWidth, imgHeight)
+  const imgdatas = context.getImageData(0, 0, imgWidth, imgHeight)
+  const imgdata = imgdatas.data
+  const newJson: any = {}
+  const length = imgdata.length
+  console.log('length:' + length)
+  for (let i = 0; i < 250; i++) {
+    if (i % 4 === 0) {
+      const alpha = Math.round((imgdata[i + 3] / 255) * 100) / 100
+      const rgba = imgdata[i] + ',' + imgdata[i + 1] + ',' + imgdata[i + 2] + ',' + alpha
+      if (!newJson[rgba]) {
+        newJson[rgba] = 1
+      } else {
+        newJson[rgba]++
+      }
+    }
+  }
+  let maxNum = 0
+  let maxVal = ''
+  for (const key in newJson) {
+    if (newJson[key] > maxNum) {
+      maxNum = newJson[key]
+      maxVal = key
+    }
+  }
+  console.log('rgba:', maxVal + ';次数:' + maxNum)
+  return maxVal
+}
+function getHex(...value: any) {
+  const r = value[0].toString(16)
+  const g = value[1].toString(16)
+  const b = value[2].toString(16)
+  let hex = r + g + b
+  if (r.slice(0, 1) == r.slice(1, 1) && g.slice(0, 1) == g.slice(1, 1) && b.slice(0, 1) == b.slice(1, 1)) {
+    hex = r.slice(0, 1) + g.slice(0, 1) + b.slice(0, 1)
+  }
+  return hex
+}
+
+interface MessageData {
+  drawing: boolean
+  blob?: Blob
+}
+
+export const usePreloadImageToTask = (
+  option: Ref<SetImgBgOption>,
+  frontColor?: Ref<string>,
+  setFrontColor?: any,
+  task?: any
+) => {
+  const initImage = async ({ task, frontColor, setFrontColor }: any) => {
+    if (!task.url) {
+      return console.warn(`task缺少url属性:`, task)
+    }
+
+    const image = await new Promise<HTMLImageElement>((resolve, reject) => {
+      const img = new Image()
+      img.src = task.url as string
+      img.onload = async () => {
+        if (frontColor && setFrontColor) {
+          try {
+            const result = await analyze(task.url, { scale: 0.2 })
+            const bgColorRgb = result[0].color
+            const bgColorNum = bgColorRgb.split('rgb(')[1].slice(0, -1)
+            const splitArr = bgColorNum.split(',')
+            const hex = getHex(Number(splitArr[0]), Number(splitArr[1]), Number(splitArr[2]))
+            const { fullPath } = useRoute()
+            const mainStore = useMainStore()
+            const userSetColor = mainStore.userMarkConfig?.[fullPath]?.frontColor
+            if (!userSetColor) {
+              setFrontColor('#' + hex)
+            }
+          } catch {}
+        }
+
+        resolve(img)
+      }
+      img.onerror = () => {
+        console.log('img error')
+        reject()
+      }
+      img.onabort = () => {
+        console.log('img aborted')
+        reject()
+      }
+    })
+    return image
+  }
+
+  const imageWorker = new TintImageWorker()
+  const drawing = ref<boolean>(false)
+  const dataUrl = ref('')
+  imageWorker.addEventListener('message', (e) => {
+    const data = e.data as MessageData
+    drawing.value = data.drawing
+    dataUrl.value = data.blob ? URL.createObjectURL(data.blob) : ''
+    task.dataUrl = dataUrl.value
+  })
+
+  nextTick(() => {
+    const opt = unref(option)
+    if (!opt.image) {
+      drawing.value = false
+      dataUrl.value = ''
+      return
+    }
+    initImage({ task, frontColor, setFrontColor })
+      .then((image) => {
+        if (!image) return
+        createImageBitmap(image).then((imageBitMap) => {
+          imageWorker.postMessage(
+            {
+              type: 'init',
+              imageBitMap,
+              basePoint: opt.basePoint,
+              enableSharpen: opt.enableSharpen,
+              distance: opt.distance,
+              scale: opt.scale,
+              rotate: opt.rotate,
+            },
+            [imageBitMap]
+          )
+        })
+      })
+      .catch((err: any) => {
+        drawing.value = false
+        dataUrl.value = ''
+        console.log('initImage catch err')
+      })
+  })
+
+  watch(
+    () => option.value.scale,
+    () => {
+      imageWorker.postMessage({ type: 'scale', scale: option.value?.scale })
+    }
+  )
+
+  watch(
+    () => option.value.rotate,
+    () => {
+      imageWorker.postMessage({ type: 'rotate', rotate: option.value?.rotate })
+    }
+  )
+
+  watch(dataUrl, (current, prev) => {
+    const imgWrapDom = document.querySelector('.img-wrap')
+    if (imgWrapDom) {
+      setTimeout(() => {
+        imgWrapDom.scrollTop = 0
+      }, 10)
+    }
+    if (prev) {
+      try {
+        URL.revokeObjectURL(prev)
+      } catch (error) {}
+    }
+  })
+
+  onScopeDispose(() => {
+    if (dataUrl.value) {
+      try {
+        URL.revokeObjectURL(dataUrl.value)
+      } catch (error) {}
+    }
+    imageWorker.terminate()
+  })
+
+  return {
+    drawing,
+    dataUrl,
+  }
+}