|
|
@@ -1,20 +1,21 @@
|
|
|
<template>
|
|
|
<div class="image-uploader">
|
|
|
- <!-- 持久化隐藏 input,避免移动端动态创建 input 重复点击失效 -->
|
|
|
+ <!-- 持久化隐藏 input,用 key 强制重建,解决移动端相机二次拍摄失效 -->
|
|
|
<input
|
|
|
+ :key="'camera-' + cameraKey"
|
|
|
ref="cameraInput"
|
|
|
type="file"
|
|
|
accept="image/*"
|
|
|
capture="environment"
|
|
|
style="display: none"
|
|
|
- @change="onFileChange"
|
|
|
+ @change="onCameraChange"
|
|
|
/>
|
|
|
<input
|
|
|
ref="albumInput"
|
|
|
type="file"
|
|
|
accept="image/*"
|
|
|
style="display: none"
|
|
|
- @change="onFileChange"
|
|
|
+ @change="onAlbumChange"
|
|
|
/>
|
|
|
|
|
|
<div class="upload-grid">
|
|
|
@@ -66,8 +67,7 @@ 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 cameraKey = ref(0)
|
|
|
|
|
|
const actions = [
|
|
|
{ name: 'camera', description: '拍摄照片' },
|
|
|
@@ -86,12 +86,9 @@ function onSelect(action: { name: string }) {
|
|
|
showAction.value = false
|
|
|
if (uploading.value) return
|
|
|
|
|
|
- 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
|
|
|
@@ -100,21 +97,34 @@ function onSelect(action: { name: string }) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function onFileChange(e: Event) {
|
|
|
+async function onCameraChange(e: Event) {
|
|
|
const input = e.target as HTMLInputElement
|
|
|
const files = input.files
|
|
|
if (!files?.length) {
|
|
|
- // 用户取消,重置 input 以便下次复用
|
|
|
input.value = ''
|
|
|
return
|
|
|
}
|
|
|
+ await uploadFiles(Array.from(files))
|
|
|
+ // 移动端相机 input 需要重建 DOM 才能再次触发
|
|
|
+ cameraKey.value++
|
|
|
+}
|
|
|
|
|
|
+async function onAlbumChange(e: Event) {
|
|
|
+ const input = e.target as HTMLInputElement
|
|
|
+ const files = input.files
|
|
|
+ if (!files?.length) {
|
|
|
+ input.value = ''
|
|
|
+ return
|
|
|
+ }
|
|
|
+ await uploadFiles(Array.from(files))
|
|
|
+ input.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+async function uploadFiles(files: File[]) {
|
|
|
uploading.value = true
|
|
|
try {
|
|
|
showToast('上传中...')
|
|
|
- const compressed = await Promise.all(
|
|
|
- Array.from(files).map((f) => compressImage(f))
|
|
|
- )
|
|
|
+ const compressed = await Promise.all(files.map(compressImage))
|
|
|
const urls = await Promise.all(compressed.map(uploadImage))
|
|
|
emit('update:modelValue', [...props.modelValue, ...urls])
|
|
|
} catch (err: any) {
|
|
|
@@ -123,8 +133,6 @@ async function onFileChange(e: Event) {
|
|
|
}
|
|
|
} finally {
|
|
|
uploading.value = false
|
|
|
- // 重置 input,否则重复选同一文件不会触发 change
|
|
|
- input.value = ''
|
|
|
}
|
|
|
}
|
|
|
</script>
|