优化二维码 OCR 上传体验:并发控制、状态清理、预览修复
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user