瀏覽代碼

fix: 修复移动端上传图片第二次点击无效的问题

- ImageUploader 改为模板持久化 input 元素,避免动态创建被浏览器限制
- 导出 compressImage 供上传前压缩
- 选完文件后重置 input.value 以支持重复选择
- README 增加构建打包命令

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tanlie 3 周之前
父節點
當前提交
d4d06053da
共有 3 個文件被更改,包括 54 次插入9 次删除
  1. 53 8
      wishing-tree-h5/src/components/ImageUploader.vue
  2. 1 1
      wishing-tree-h5/src/utils/camera.ts
  3. 二進制
      wishing-tree-h5/wish.zip

+ 53 - 8
wishing-tree-h5/src/components/ImageUploader.vue

@@ -1,5 +1,22 @@
 <template>
   <div class="image-uploader">
+    <!-- 持久化隐藏 input,避免移动端动态创建 input 重复点击失效 -->
+    <input
+      ref="cameraInput"
+      type="file"
+      accept="image/*"
+      capture="environment"
+      style="display: none"
+      @change="onFileChange"
+    />
+    <input
+      ref="albumInput"
+      type="file"
+      accept="image/*"
+      style="display: none"
+      @change="onFileChange"
+    />
+
     <div class="upload-grid">
       <div
         v-for="(img, i) in images"
@@ -35,8 +52,8 @@
 <script setup lang="ts">
 import { ref, computed } from 'vue'
 import { showToast } from 'vant'
-import { takePhoto, pickFromAlbum } from '@/utils/camera'
 import { uploadImage } from '@/api/upload'
+import { compressImage } from '@/utils/camera'
 
 const props = withDefaults(defineProps<{
   modelValue: string[]
@@ -47,6 +64,10 @@ const emit = defineEmits<{ 'update:modelValue': [v: string[]] }>()
 
 const showAction = ref(false)
 const uploading = ref(false)
+const cameraInput = ref<HTMLInputElement | null>(null)
+const albumInput = ref<HTMLInputElement | null>(null)
+// 记录本次选择的模式,在 onFileChange 中区分
+let currentMode: 'camera' | 'album' = 'album'
 
 const actions = [
   { name: 'camera', description: '拍摄照片' },
@@ -61,18 +82,40 @@ function removeImage(i: number) {
   emit('update:modelValue', arr)
 }
 
-async function onSelect(action: { name: string }) {
+function onSelect(action: { name: string }) {
   showAction.value = false
   if (uploading.value) return
-  uploading.value = true
 
-  try {
-    const files = action.name === 'camera'
-      ? [await takePhoto()]
-      : await pickFromAlbum(props.max - props.modelValue.length > 1)
+  currentMode = action.name as 'camera' | 'album'
 
+  if (action.name === 'camera') {
+    cameraInput.value?.click()
+  } else {
+    // 设置多选
+    const input = albumInput.value
+    if (input) {
+      input.multiple = props.max - props.modelValue.length > 1
+      input.click()
+    }
+  }
+}
+
+async function onFileChange(e: Event) {
+  const input = e.target as HTMLInputElement
+  const files = input.files
+  if (!files?.length) {
+    // 用户取消,重置 input 以便下次复用
+    input.value = ''
+    return
+  }
+
+  uploading.value = true
+  try {
     showToast('上传中...')
-    const urls = await Promise.all(files.map(uploadImage))
+    const compressed = await Promise.all(
+      Array.from(files).map((f) => compressImage(f))
+    )
+    const urls = await Promise.all(compressed.map(uploadImage))
     emit('update:modelValue', [...props.modelValue, ...urls])
   } catch (err: any) {
     if (err.message !== '未选择图片') {
@@ -80,6 +123,8 @@ async function onSelect(action: { name: string }) {
     }
   } finally {
     uploading.value = false
+    // 重置 input,否则重复选同一文件不会触发 change
+    input.value = ''
   }
 }
 </script>

+ 1 - 1
wishing-tree-h5/src/utils/camera.ts

@@ -29,7 +29,7 @@ export function pickFromAlbum(multiple = false): Promise<File[]> {
   })
 }
 
-function compressImage(file: File): Promise<File> {
+export function compressImage(file: File): Promise<File> {
   return new Promise((resolve) => {
     const reader = new FileReader()
     reader.onload = (e) => {

二進制
wishing-tree-h5/wish.zip