diff --git a/static/src/components/overlay/ImagePicker.vue b/static/src/components/overlay/ImagePicker.vue index 59520b5..4bab66b 100644 --- a/static/src/components/overlay/ImagePicker.vue +++ b/static/src/components/overlay/ImagePicker.vue @@ -79,12 +79,14 @@ const props = withDefaults( const emit = defineEmits<{ (e: 'close'): void; (e: 'confirm', list: string[]): void; - (e: 'local-files', files: FileList | null): void; + (e: 'local-files', files: File[] | null): void; }>(); const selectedSet = ref>(new Set(props.initialSelected || [])); const localInput = ref(null); +let ignoreBackdropClickUntil = 0; + watch( () => props.initialSelected, (val) => { @@ -106,25 +108,35 @@ const toggle = (path: string) => { selectedSet.value = set; }; -const close = () => emit('close'); +const close = () => { + if (Date.now() < ignoreBackdropClickUntil) return; + emit('close'); +}; const confirm = () => emit('confirm', Array.from(selectedSet.value)); const triggerLocal = () => { if (props.uploading) return; + // 屏蔽接下来 500ms 内的 backdrop click,防止系统文件选择器关闭后的 touch 穿透误关 overlay + ignoreBackdropClickUntil = Date.now() + 500; localInput.value?.click(); }; const onLocalChange = (event: Event) => { const target = event.target as HTMLInputElement; - const files = target?.files || null; + const rawFiles = target?.files; + // 某些 WebView(尤其国产手机)在 input value 被清空后 FileList 会失效,先深拷贝 + const files = rawFiles && rawFiles.length ? Array.from(rawFiles) : null; if (!files || files.length === 0) { console.warn('[ImagePicker] change fired but files empty — likely WebView compat issue'); + emit('local-files', null); + } else { + emit('local-files', files); } - emit('local-files', files); - if (target) { - target.value = ''; - } + // 延迟清空:国产手机 WebView 中 files 填充可能是异步的,立即清空会导致 files 被截断 + setTimeout(() => { + if (target) target.value = ''; + }, 200); }; const previewUrl = (path: string) => `/api/gui/files/download?path=${encodeURIComponent(path)}`; diff --git a/static/src/components/overlay/VideoPicker.vue b/static/src/components/overlay/VideoPicker.vue index 1013c0c..40bf1ed 100644 --- a/static/src/components/overlay/VideoPicker.vue +++ b/static/src/components/overlay/VideoPicker.vue @@ -81,12 +81,14 @@ const props = withDefaults( const emit = defineEmits<{ (e: 'close'): void; (e: 'confirm', list: string[]): void; - (e: 'local-files', files: FileList | null): void; + (e: 'local-files', files: File[] | null): void; }>(); const selectedSet = ref>(new Set(props.initialSelected || [])); const localInput = ref(null); +let ignoreBackdropClickUntil = 0; + watch( () => props.initialSelected, (val) => { @@ -108,18 +110,25 @@ const toggle = (path: string) => { selectedSet.value = set; }; -const close = () => emit('close'); +const close = () => { + if (Date.now() < ignoreBackdropClickUntil) return; + emit('close'); +}; const confirm = () => emit('confirm', Array.from(selectedSet.value)); const triggerLocal = () => { if (props.uploading) return; + // 屏蔽接下来 500ms 内的 backdrop click,防止系统文件选择器关闭后的 touch 穿透误关 overlay + ignoreBackdropClickUntil = Date.now() + 500; localInput.value?.click(); }; const onLocalChange = (event: Event) => { const target = event.target as HTMLInputElement; - const files = target?.files || null; + const rawFiles = target?.files; + // 某些 WebView(尤其国产手机)在 input value 被清空后 FileList 会失效,先深拷贝 + const files = rawFiles && rawFiles.length ? Array.from(rawFiles) : null; if (!files || files.length === 0) { console.warn('[VideoPicker] change fired but files empty — likely WebView compat issue'); }