fix: 优化作业拍照上传

This commit is contained in:
shawko 2026-05-11 18:43:15 +08:00
parent a6fde98a35
commit 5ec9b711aa
3 changed files with 89 additions and 18 deletions

View File

@ -36,7 +36,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, watch, nextTick } from 'vue'
import { ref, reactive, watch, nextTick, onBeforeUnmount } from 'vue'
import 'vue-cropper/dist/index.css'
import { VueCropper } from 'vue-cropper'
@ -54,6 +54,8 @@ const imgLoading = ref(true)
// /
const MAX_IMG_DIMENSION = 1280
const IMAGE_LOAD_TIMEOUT = 10000
const CROP_CONFIRM_TIMEOUT = 8000
const option = reactive({
size: 0.85,
@ -77,6 +79,20 @@ const option = reactive({
// props.img
let loadToken = 0
let imageLoadTimer: ReturnType<typeof setTimeout> | null = null
let cropConfirmTimer: ReturnType<typeof setTimeout> | null = null
function clearImageLoadTimer() {
if (!imageLoadTimer) return
clearTimeout(imageLoadTimer)
imageLoadTimer = null
}
function clearCropConfirmTimer() {
if (!cropConfirmTimer) return
clearTimeout(cropConfirmTimer)
cropConfirmTimer = null
}
function readFileAsDataURL(file: Blob | File): Promise<string> {
return new Promise((resolve, reject) => {
@ -145,16 +161,29 @@ watch(
() => props.img,
async (val: string | Blob | File | undefined) => {
//
clearImageLoadTimer()
imgUrl.value = ''
imgLoading.value = true
if (!val) return
if (!val) {
imgLoading.value = false
return
}
const myToken = ++loadToken
try {
const url = await downscaleImage(val)
//
if (myToken !== loadToken) return
if (!url) {
imgLoading.value = false
return
}
imgUrl.value = url
imageLoadTimer = setTimeout(() => {
if (myToken !== loadToken) return
console.warn('cropper image load timeout')
imgLoading.value = false
}, IMAGE_LOAD_TIMEOUT)
} catch (e) {
console.error('cropper image load error', e)
if (myToken === loadToken) imgLoading.value = false
@ -165,6 +194,7 @@ watch(
function imgLoad(msg: string) {
if (msg === 'success') {
clearImageLoadTimer()
imgLoading.value = false
nextTick(() => {
if (cropperRef.value) {
@ -177,7 +207,11 @@ function imgLoad(msg: string) {
})
}
})
return
}
clearImageLoadTimer()
console.warn('cropper image load failed', msg)
imgLoading.value = false
}
function rotateLeft() {
@ -189,18 +223,33 @@ function rotateRight() {
}
function handleCancel() {
clearCropConfirmTimer()
loading.value = false
emit('update:visible', false)
emit('cancel')
}
function handleConfirm() {
if (imgLoading.value || loading.value) return
if (!cropperRef.value?.getCropBlob) return
loading.value = true
cropperRef.value.getCropBlob((data: Blob) => {
cropConfirmTimer = setTimeout(() => {
console.warn('cropper confirm timeout')
loading.value = false
}, CROP_CONFIRM_TIMEOUT)
cropperRef.value.getCropBlob((data: Blob) => {
clearCropConfirmTimer()
loading.value = false
if (!data) return
emit('confirm', data)
emit('update:visible', false)
})
}
onBeforeUnmount(() => {
clearImageLoadTimer()
clearCropConfirmTimer()
})
</script>
<style scoped lang="scss">

View File

@ -23,8 +23,8 @@ const OSS_URL = import.meta.env.VITE_OSS_URL
const { token } = userStore()
function onUpload(options: any) {
return typeof uploadFile === 'function'
? uploadFile({
return typeof props.uploadFile === 'function'
? props.uploadFile({
...options,
isAndroid: 0,
})

View File

@ -64,6 +64,18 @@ interface WorkQuestion extends IQuestion {
}
const paperList = ref<WorkQuestion[]>([])
const paperInfo = ref<any>({})
const UPLOAD_TIMEOUT = 30000
function withUploadTimeout<T>(task: Promise<T>, timeout = UPLOAD_TIMEOUT) {
let timer: ReturnType<typeof setTimeout> | null = null
const timeoutTask = new Promise<T>((_, reject) => {
timer = setTimeout(() => reject(new Error('upload timeout')), timeout)
})
return Promise.race<T>([task, timeoutTask]).finally(() => {
if (!timer) return
clearTimeout(timer)
})
}
function normalizePaperList(list: unknown, markUnLook = false): WorkQuestion[] {
if (!Array.isArray(list)) return []
@ -224,13 +236,18 @@ async function uploadFile(options: anyObj) {
const currentQuestion = paperList.value[activeIdx.value] as anyObj | undefined
if (!currentQuestion) return
const currentTitleId = getQuestionKey(currentQuestion)
if (currentQuestion.uploadLoading) {
XMessage.error('上传中,请稍后~')
return
}
setUploadLoadingByTitleId(currentTitleId, true)
try {
let path = ''
if (options.isAndroid) {
if (options.submitAnswerPicOriginal) {
const res = await $XXL.uploadFile({
const res = await withUploadTimeout(
$XXL.uploadFile({
url: `${import.meta.env.VITE_SERVER_URL}/sysFileInfo/tenUploadAll`,
header: {
Authorization: token,
@ -238,20 +255,21 @@ async function uploadFile(options: anyObj) {
},
filePath: options.file,
fileKey: 'file',
})
path = res.url
}),
)
path = extractUploadPath(res)
}
} else {
const formData = new FormData()
formData.append('file', options.file)
const { data: res } = await globalApi.uploadFile(formData)
const { data: res } = await withUploadTimeout<any>(globalApi.uploadFile(formData))
path = extractUploadPath(res)
}
path = String(path || '')
.replace(/[`]/g, '')
.replace(/http:\/\//gi, 'https://')
.trim()
if (!options.isAndroid && !path) {
if ((options.submitAnswerPicOriginal || !options.isAndroid) && !path) {
hud.error('上传成功但未获取到图片地址,请重试')
return
}
@ -291,7 +309,11 @@ async function uploadFile(options: anyObj) {
}
} catch (error) {
console.error('uploadFile error', error)
hud.error('网络异常,请检查网络~')
hud.error(
error instanceof Error && error.message === 'upload timeout'
? '上传超时,请重试~'
: '网络异常,请检查网络~',
)
} finally {
const finalIdx = paperList.value.findIndex(i =>
isSameQuestionKey(getQuestionKey(i as anyObj), currentTitleId),