优化二维码 OCR 上传体验:并发控制、状态清理、预览修复

This commit is contained in:
yml2213
2026-06-28 20:20:37 +08:00
parent b124c74a46
commit c6c6a92645
@@ -67,6 +67,7 @@ const uploadVisible = ref(false)
const uploadPendingCount = ref(0) const uploadPendingCount = ref(0)
const uploadSaving = ref(false) const uploadSaving = ref(false)
type UploadedQrImage = { type UploadedQrImage = {
_id: string
url: string url: string
previewUrl: string previewUrl: string
file: File file: File
@@ -230,6 +231,37 @@ function parseGroupNameFromOcrText(text: string) {
return candidates.find(line => line.includes('群')) || candidates.find(line => /[-]/.test(line)) || candidates[0] || '' return candidates.find(line => line.includes('群')) || candidates.find(line => /[-]/.test(line)) || candidates[0] || ''
} }
const ocrConcurrencyLimit = 4
let ocrRunningCount = 0
const ocrWaitQueue: (() => void)[] = []
function ocrAcquireSlot(): Promise<void> {
if (ocrRunningCount < ocrConcurrencyLimit) {
ocrRunningCount++
return Promise.resolve()
}
return new Promise(resolve => {
ocrWaitQueue.push(() => {
ocrRunningCount++
resolve()
})
})
}
function ocrReleaseSlot() {
ocrRunningCount = Math.max(0, ocrRunningCount - 1)
const next = ocrWaitQueue.shift()
if (next) next()
}
function ocrResetSlots() {
ocrRunningCount = 0
while (ocrWaitQueue.length > 0) {
const next = ocrWaitQueue.shift()
next?.()
}
}
async function recognizeGroupName(file: File) { async function recognizeGroupName(file: File) {
const { job_id } = await submitOCRJob(file) const { job_id } = await submitOCRJob(file)
const delays = [1000, 2000, 3000, 3000, 3000] const delays = [1000, 2000, 3000, 3000, 3000]
@@ -246,6 +278,15 @@ async function recognizeGroupName(file: File) {
throw new Error('OCR 识别超时') throw new Error('OCR 识别超时')
} }
async function recognizeGroupNameWithLimit(file: File) {
await ocrAcquireSlot()
try {
return await recognizeGroupName(file)
} finally {
ocrReleaseSlot()
}
}
// 上传:逐个上传图片拿 URL // 上传:逐个上传图片拿 URL
async function handleFileUpload(options: { file: File }) { async function handleFileUpload(options: { file: File }) {
if (uploadedImages.value.length + uploadPendingCount.value >= maxBatchUploadCount) { if (uploadedImages.value.length + uploadPendingCount.value >= maxBatchUploadCount) {
@@ -260,6 +301,7 @@ async function handleFileUpload(options: { file: File }) {
uploadPendingCount.value += 1 uploadPendingCount.value += 1
try { try {
const item: UploadedQrImage = { const item: UploadedQrImage = {
_id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
url: '', url: '',
previewUrl: URL.createObjectURL(options.file), previewUrl: URL.createObjectURL(options.file),
file: options.file, file: options.file,
@@ -270,7 +312,7 @@ async function handleFileUpload(options: { file: File }) {
const [uploaded, groupName] = await Promise.all([ const [uploaded, groupName] = await Promise.all([
uploadAdminFile(options.file, 'qrcode'), uploadAdminFile(options.file, 'qrcode'),
recognizeGroupName(options.file).catch(error => { recognizeGroupNameWithLimit(options.file).catch(error => {
console.warn('二维码群名 OCR 失败', error) console.warn('二维码群名 OCR 失败', error)
ElMessage.warning(readError(error, '二维码群名 OCR 失败')) ElMessage.warning(readError(error, '二维码群名 OCR 失败'))
return '' return ''
@@ -307,6 +349,8 @@ function clearUploadedImages() {
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl) if (item.previewUrl) URL.revokeObjectURL(item.previewUrl)
}) })
uploadedImages.value = [] uploadedImages.value = []
uploadPendingCount.value = 0
ocrResetSlots()
} }
function handleUploadExceed() { function handleUploadExceed() {
@@ -778,7 +822,7 @@ onMounted(reloadAll)
</template> </template>
</el-upload> </el-upload>
<div v-if="uploadedImages.length" class="uploaded-list"> <div v-if="uploadedImages.length" class="uploaded-list">
<div v-for="(img, idx) in uploadedImages" :key="idx" class="uploaded-item"> <div v-for="(img, idx) in uploadedImages" :key="img._id" class="uploaded-item">
<div class="uploaded-main"> <div class="uploaded-main">
<div class="uploaded-thumb"> <div class="uploaded-thumb">
<AuthImage <AuthImage
@@ -786,7 +830,7 @@ onMounted(reloadAll)
:source="img.url" :source="img.url"
fit="cover" fit="cover"
:image-style="{ width: '52px', height: '52px', borderRadius: '6px' }" :image-style="{ width: '52px', height: '52px', borderRadius: '6px' }"
:preview-src-list="imagePreviewSources()" :preview-src-list="[img.url]"
/> />
<img <img
v-else v-else