feat: improve image browsing experience

This commit is contained in:
2026-05-21 19:56:42 +08:00
parent 7504cce7f1
commit 6d8acce295
6 changed files with 661 additions and 32 deletions
+97 -3
View File
@@ -31,6 +31,8 @@ export interface UploadResult {
}
let nextId = 0
const THUMBNAIL_MAX_EDGE = 640
const THUMBNAIL_QUALITY = 0.72
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
@@ -41,6 +43,84 @@ function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
})
}
function loadImageElement(file: File): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const objectUrl = URL.createObjectURL(file)
const image = new Image()
image.onload = () => {
URL.revokeObjectURL(objectUrl)
resolve(image)
}
image.onerror = () => {
URL.revokeObjectURL(objectUrl)
reject(new Error('图片读取失败'))
}
image.src = objectUrl
})
}
function getResizedDimensions(width: number, height: number, maxEdge: number) {
const largestEdge = Math.max(width, height)
if (largestEdge <= maxEdge) {
return { width, height }
}
const scale = maxEdge / largestEdge
return {
width: Math.round(width * scale),
height: Math.round(height * scale),
}
}
function fileBaseName(name: string) {
const idx = name.lastIndexOf('.')
return idx === -1 ? name : name.slice(0, idx)
}
async function renderJpegFile(
image: HTMLImageElement,
sourceFile: File,
maxEdge: number,
quality: number,
suffix = '',
) {
const { width, height } = getResizedDimensions(image.naturalWidth || image.width, image.naturalHeight || image.height, maxEdge)
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const context = canvas.getContext('2d')
if (!context) {
throw new Error('图片压缩失败')
}
context.drawImage(image, 0, 0, width, height)
const blob = await new Promise<Blob | null>(resolve => {
canvas.toBlob(resolve, 'image/jpeg', quality)
})
if (!blob) {
throw new Error('图片压缩失败')
}
return new File([blob], `${fileBaseName(sourceFile.name)}${suffix}.jpg`, {
type: 'image/jpeg',
lastModified: Date.now(),
})
}
async function createUploadAssets(file: File) {
const image = await loadImageElement(file)
return {
thumbnailFile: await renderJpegFile(image, file, THUMBNAIL_MAX_EDGE, THUMBNAIL_QUALITY, '-thumb'),
}
}
function extractExifDate(buffer: ArrayBuffer): string | null {
const view = new DataView(buffer)
if (view.getUint16(0, false) !== 0xffd8) return null
@@ -253,18 +333,32 @@ export function useUpload() {
overallProgress.value = Math.round(i / items.value.length * 100)
const ext = item.file.name.split('.').pop() || 'jpg'
const basePath = `${userId}/${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const ext = item.file.name.split('.').pop() || 'jpg'
const imagePath = `${basePath}.${ext}`
const thumbnailPath = `${basePath}-thumb.jpg`
const { thumbnailFile } = await createUploadAssets(item.file)
const { error: imgError } = await supabase.storage
.from('clouds')
.upload(imagePath, item.file, { upsert: false })
.upload(imagePath, item.file, {
upsert: false,
contentType: item.file.type,
})
if (imgError) throw imgError
const { error: thumbError } = await supabase.storage
.from('clouds')
.upload(thumbnailPath, thumbnailFile, {
upsert: false,
contentType: thumbnailFile.type,
})
if (thumbError) throw thumbError
overallProgress.value = Math.round((i + 0.5) / items.value.length * 100)
const { data: { publicUrl: imageUrl } } = supabase.storage.from('clouds').getPublicUrl(imagePath)
const { data: { publicUrl: thumbnailUrl } } = supabase.storage.from('clouds').getPublicUrl(thumbnailPath)
const latitude = item.latitude ? blurCoordinate(item.latitude) : null
const longitude = item.longitude ? blurCoordinate(item.longitude) : null
@@ -276,7 +370,7 @@ export function useUpload() {
cloud_type_id: item.cloudCategoryId === 'other' ? null : item.cloudCategoryId,
custom_cloud_type: item.cloudCategoryId === 'other' ? (item.customCloudType.trim() || null) : null,
image_url: imageUrl,
thumbnail_url: null,
thumbnail_url: thumbnailUrl,
latitude,
longitude,
location_name: item.locationName || null,