修复部分浏览器销假/请假上传图片「选完无任何反应」
ci/woodpecker/tag/build Pipeline was successful

用户反馈:销假时选完图片后无转圈、无预览、无报错,服务端也收不到
upload-policy 请求(访问日志可证)。

原实现用游离的 input(createElement 后未挂 DOM)且 accept 限定
image/png,image/jpeg,image/jpg;在部分第三方内核(如 iPhone 夸克)
下,游离 input 不触发 onchange,iOS 相册的 HEIC 等格式也会被判为
不匹配,导致 input.files 为空 → 命中 `if (!file) return` 静默返回。

抽出 pickImageFiles() 统一修复:
- accept 改 image/*(与首页上传一致);
- input 挂到 DOM,用完移除;
- 监听 cancel 区分「用户取消」(静默)与「选了但读不到文件」(可见提示);
- 销假图片、请假附件两处共用,并修正 uploading 标志在提前 return 之后才置位的问题。
This commit is contained in:
becomer
2026-10-08 23:02:43 +08:00
parent d56769b265
commit 1724a37bcf
+79 -54
View File
@@ -1040,28 +1040,56 @@ async function uploadImageToObs(file: File): Promise<string | null> {
return previewJson.data.previewUrl
}
function handleImageUpload() {
if (uploadingImage.value) return
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/png,image/jpeg,image/jpg'
input.onchange = async () => {
const file = input.files?.[0]
if (!file) return
uploadingImage.value = true
try {
const previewUrl = await uploadImageToObs(file)
if (!previewUrl) return
form.value.imageUrl = previewUrl
message.success('图片上传成功')
} catch (e: any) {
message.error('图片上传失败: ' + (e?.message || '未知错误'))
} finally {
uploadingImage.value = false
// 打开系统文件选择器。两点与原实现不同,用于修复部分浏览器「选完图片无任何反应」:
// 1. input 必须挂到 DOM:部分第三方内核(如 iPhone 夸克)对游离 input 不触发 onchange;
// 2. accept 用 image/*:旧写法限定 png/jpeg,iOS 相册的 HEIC 等格式会被判为不匹配,
// 选完拿不到 file(表现为无转圈、无预览、无报错、也无任何请求)。
// 返回 picked=false 表示用户取消(静默返回);picked=true 但 files 为空表示
// 选择器返回了但不含可用文件,调用方应给出可见提示,避免再次静默失败。
function pickImageFiles(multiple = false): Promise<{ picked: boolean; files: File[] }> {
return new Promise((resolve) => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.multiple = multiple
input.style.position = 'fixed'
input.style.left = '-9999px'
let settled = false
const finish = (picked: boolean, files: File[]) => {
if (settled) return
settled = true
input.remove()
resolve({ picked, files })
}
input.onchange = () => finish(true, Array.from(input.files || []))
// 用户取消:iOS 16+/现代内核会触发 cancel,提前清理避免 input 泄漏
input.addEventListener('cancel', () => finish(false, []))
document.body.appendChild(input)
input.click()
})
}
async function handleImageUpload() {
if (uploadingImage.value) return
const { picked, files } = await pickImageFiles()
if (!picked) return // 用户取消,静默
const file = files[0]
if (!file) {
message.warning('未能读取所选图片,请重试或换一张图片')
return
}
uploadingImage.value = true
try {
const previewUrl = await uploadImageToObs(file)
if (!previewUrl) return
form.value.imageUrl = previewUrl
message.success('图片上传成功')
} catch (e: any) {
message.error('图片上传失败: ' + (e?.message || '未知错误'))
} finally {
uploadingImage.value = false
}
input.click()
}
function removeImage() {
@@ -1446,42 +1474,39 @@ async function loadApplyApprovers() {
}
}
function handleApplyImageUpload() {
async function handleApplyImageUpload() {
if (uploadingApplyImage.value) return
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/png,image/jpeg,image/jpg'
input.multiple = true
input.onchange = async () => {
const files = Array.from(input.files || [])
if (files.length === 0) return
const remain = applyAttachLimit.value - applyForm.value.imageUrls.length
if (remain <= 0) {
message.warning(`附件最多 ${applyAttachLimit.value} 张`)
return
}
// 超出上限的多选文件直接丢弃(避免静默只传部分却无提示)
const picked = files.slice(0, remain)
if (files.length > remain) {
message.warning(`附件最多 ${applyAttachLimit.value} 张,本次仅上传前 ${picked.length} 张`)
}
uploadingApplyImage.value = true
try {
let uploaded = 0
for (const file of picked) {
const previewUrl = await uploadImageToObs(file)
if (!previewUrl) continue
applyForm.value.imageUrls = [...applyForm.value.imageUrls, previewUrl]
uploaded++
}
if (uploaded > 0) message.success('图片上传成功')
} catch (e: any) {
message.error('图片上传失败: ' + (e?.message || '未知错误'))
} finally {
uploadingApplyImage.value = false
}
const { picked, files } = await pickImageFiles(true)
if (!picked) return // 用户取消,静默
if (files.length === 0) {
message.warning('未能读取所选图片,请重试或换一张图片')
return
}
const remain = applyAttachLimit.value - applyForm.value.imageUrls.length
if (remain <= 0) {
message.warning(`附件最多 ${applyAttachLimit.value} 张`)
return
}
// 超出上限的多选文件直接丢弃(避免静默只传部分却无提示)
const pickedFiles = files.slice(0, remain)
if (files.length > remain) {
message.warning(`附件最多 ${applyAttachLimit.value} 张,本次仅上传前 ${pickedFiles.length} 张`)
}
uploadingApplyImage.value = true
try {
let uploaded = 0
for (const file of pickedFiles) {
const previewUrl = await uploadImageToObs(file)
if (!previewUrl) continue
applyForm.value.imageUrls = [...applyForm.value.imageUrls, previewUrl]
uploaded++
}
if (uploaded > 0) message.success('图片上传成功')
} catch (e: any) {
message.error('图片上传失败: ' + (e?.message || '未知错误'))
} finally {
uploadingApplyImage.value = false
}
input.click()
}
function removeApplyImage(idx: number) {