| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import { ref, nextTick, onBeforeUnmount, Ref } from 'vue'
- export function useThumbnails(getFilePath: (p: string) => string) {
- const thumbnailMap = ref<Record<string, string | null>>({})
- const generatingMap = new Map<string, AbortController>()
- const placeholderDataUrl =
- 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200"><rect width="100%" height="100%" fill="#F5F7FA"/></svg>'
- function isGenerating(key: string) {
- return generatingMap.has(key)
- }
- function startGenerating(key: string) {
- generatingMap.set(key, new AbortController())
- }
- function stopGenerating(key: string) {
- generatingMap.get(key)?.abort()
- generatingMap.delete(key)
- }
- async function generateThumbnail(imagePath: string, actionId: string, maxW = 400, maxH = 400) {
- if (!imagePath) return
- const key = `${imagePath}::${actionId}`
- if (thumbnailMap.value[imagePath] !== undefined) return
- if (isGenerating(key)) return
- startGenerating(key)
- try {
- const src = getFilePath(imagePath)
- const img = new Image()
- img.src = src
- await new Promise<void>((resolve, reject) => {
- img.onload = () => resolve()
- img.onerror = () => reject(new Error('image load error'))
- })
- const w = img.naturalWidth || img.width
- const h = img.naturalHeight || img.height
- if (!w || !h) {
- thumbnailMap.value[imagePath] = null
- return
- }
- const ratio = Math.min(1, maxW / w, maxH / h)
- const tw = Math.max(1, Math.round(w * ratio))
- const th = Math.max(1, Math.round(h * ratio))
- const canvas = document.createElement('canvas')
- canvas.width = tw
- canvas.height = th
- const ctx = canvas.getContext('2d')
- if (!ctx) {
- thumbnailMap.value[imagePath] = null
- return
- }
- ctx.drawImage(img, 0, 0, tw, th)
- const dataUrl = canvas.toDataURL('image/jpeg', 0.7)
- thumbnailMap.value[imagePath] = dataUrl
- } catch (e) {
- console.warn('generateThumbnail error', e)
- thumbnailMap.value[imagePath] = null
- } finally {
- stopGenerating(key)
- }
- }
- let observer: IntersectionObserver | null = null
- function observe(containerRef: Ref<HTMLElement | null>, goodsListRef: Ref<any[]>) {
- if (!containerRef.value) return
- if (observer) observer.disconnect()
- observer = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- const el = entry.target as HTMLElement
- const goodsNo = el.getAttribute('data-goods-art-no')
- if (!goodsNo) return
- const item = goodsListRef.value.find((g: any) => g.goods_art_no === goodsNo)
- if (item && Array.isArray(item.items)) {
- item.items.forEach((img: any) => {
- const p = img?.PhotoRecord?.image_path
- const actionId = img?.action_id || img?.action_name || ''
- if (p && thumbnailMap.value[p] === undefined) {
- generateThumbnail(p, actionId, 400, 400)
- }
- })
- }
- observer?.unobserve(el)
- }
- })
- },
- { root: null, rootMargin: '300px', threshold: 0.05 },
- )
- nextTick(() => {
- const nodes = containerRef.value!.querySelectorAll('.history-item, .goods-item')
- nodes.forEach((n) => {
- if (n instanceof HTMLElement) {
- const keyEl = n.querySelector('.goods-art-no') as HTMLElement | null
- const key = keyEl ? keyEl.innerText : n.getAttribute('data-goods-art-no') || ''
- if (key) n.setAttribute('data-goods-art-no', key)
- observer?.observe(n)
- }
- })
- })
- }
- function stop() {
- if (observer) {
- observer.disconnect()
- observer = null
- }
- generatingMap.forEach((ctrl) => ctrl.abort())
- generatingMap.clear()
- }
- onBeforeUnmount(() => {
- stop()
- })
- return {
- thumbnailMap,
- generateThumbnail,
- observe,
- stop,
- placeholderDataUrl,
- }
- }
|