fix: 修复画板无法上传问题
This commit is contained in:
parent
d066302c31
commit
4805c7ee35
@ -94,6 +94,17 @@ function handleSave() {
|
||||
}
|
||||
}
|
||||
|
||||
function dataUrlToBlob(dataUrl: string) {
|
||||
const [header, body = ''] = dataUrl.split(',')
|
||||
const mime = header.match(/data:(.*?);base64/)?.[1] || 'image/jpeg'
|
||||
const binary = atob(body)
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i)
|
||||
}
|
||||
return new Blob([bytes], { type: mime })
|
||||
}
|
||||
|
||||
function handleSaveAsync(): Promise<{ blob: Blob | null; data: string | null }> {
|
||||
return new Promise(resolve => {
|
||||
if (signaturePad.isEmpty() && !hasBackground) {
|
||||
@ -102,7 +113,17 @@ function handleSaveAsync(): Promise<{ blob: Blob | null; data: string | null }>
|
||||
}
|
||||
const canvas = canvasRef.value as HTMLCanvasElement
|
||||
const pointData = JSON.stringify(signaturePad.toData())
|
||||
canvas.toBlob(blob => resolve({ blob, data: pointData }), 'image/jpeg', 0.85)
|
||||
const resolveWithFallback = (blob: Blob | null) => {
|
||||
resolve({
|
||||
blob: blob || dataUrlToBlob(canvas.toDataURL('image/jpeg', 0.85)),
|
||||
data: pointData,
|
||||
})
|
||||
}
|
||||
if (typeof canvas.toBlob === 'function') {
|
||||
canvas.toBlob(resolveWithFallback, 'image/jpeg', 0.85)
|
||||
} else {
|
||||
resolveWithFallback(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -174,7 +195,8 @@ onMounted(() => {
|
||||
canvasRef.value.height = canvasPanelRef.value.clientHeight
|
||||
signaturePad = new SignaturePad(canvasRef.value, {
|
||||
backgroundColor: props.bg,
|
||||
throttle: 0,
|
||||
throttle: 16,
|
||||
minDistance: 1,
|
||||
})
|
||||
fillCanvasBackground()
|
||||
}
|
||||
@ -203,9 +225,16 @@ defineExpose({
|
||||
.signature-pad {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
.canvas-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
canvas {
|
||||
display: block;
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { XMessage, debounce } from '@/hooks'
|
||||
import { $XXL, isWebEvn } from '@/utils'
|
||||
import CameraPreview from './CameraPreview.vue'
|
||||
import ImageCropper from '@/components/ImageCropper/index.vue'
|
||||
|
||||
@ -63,9 +62,19 @@ async function save() {
|
||||
return XMessage.error('内容为空,无法保存')
|
||||
}
|
||||
|
||||
showCanvas.value = false
|
||||
if (typeof props.uploadFile !== 'function') {
|
||||
return XMessage.error('上传方法不存在,请重试')
|
||||
}
|
||||
|
||||
props.uploadFile?.({ file: blob, submitAnswerPicOriginal: data })
|
||||
try {
|
||||
const uploadSuccess = await props.uploadFile({ file: blob, submitAnswerPicOriginal: data })
|
||||
if (uploadSuccess !== false) {
|
||||
showCanvas.value = false
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('uploadFile error', e)
|
||||
XMessage.error('上传失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
const showCropper = ref(false)
|
||||
@ -78,6 +87,7 @@ function onUploadCamera(options: any) {
|
||||
showCanvas.value = false
|
||||
uploadQueue.value.push(options)
|
||||
processQueue()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
@ -126,25 +136,7 @@ async function handleStart() {
|
||||
return XMessage.error('上传中,请稍后~')
|
||||
}
|
||||
|
||||
if (isWebEvn) {
|
||||
showCanvas.value = true
|
||||
} else {
|
||||
const paths = props.isAndroid ? props.submitAnswerPicOriginal || '' : ''
|
||||
|
||||
$XXL.showDoodle(
|
||||
{
|
||||
mode: 1,
|
||||
paths,
|
||||
},
|
||||
({ filepath, paths }: { filepath: string; paths: string }) => {
|
||||
props.uploadFile?.({
|
||||
file: filepath,
|
||||
submitAnswerPicOriginal: paths,
|
||||
isAndroid: 1,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadRef = ref()
|
||||
|
||||
@ -233,11 +233,11 @@ function setUploadLoadingByTitleId(titleId: unknown, value: boolean) {
|
||||
// 上传
|
||||
async function uploadFile(options: anyObj) {
|
||||
const currentQuestion = paperList.value[activeIdx.value] as anyObj | undefined
|
||||
if (!currentQuestion) return
|
||||
if (!currentQuestion) return false
|
||||
const currentTitleId = getQuestionKey(currentQuestion)
|
||||
if (currentQuestion.uploadLoading) {
|
||||
XMessage.error('上传中,请稍后~')
|
||||
return
|
||||
return false
|
||||
}
|
||||
setUploadLoadingByTitleId(currentTitleId, true)
|
||||
|
||||
@ -262,7 +262,7 @@ async function uploadFile(options: anyObj) {
|
||||
.trim()
|
||||
if ((options.submitAnswerPicOriginal || !options.isAndroid) && !path) {
|
||||
hud.error('上传成功但未获取到图片地址,请重试')
|
||||
return
|
||||
return false
|
||||
}
|
||||
if (options.submitAnswerPicOriginal) {
|
||||
submitQuestion({
|
||||
@ -298,13 +298,16 @@ async function uploadFile(options: anyObj) {
|
||||
await switchQuestion(activeIdx.value)
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('uploadFile error', error)
|
||||
const message = getUploadErrorMessage(error)
|
||||
console.error('uploadFile error', message, error)
|
||||
hud.error(
|
||||
error instanceof Error && error.message === 'upload timeout'
|
||||
? '上传超时,请重试~'
|
||||
: '网络异常,请检查网络~',
|
||||
)
|
||||
return false
|
||||
} finally {
|
||||
const finalIdx = paperList.value.findIndex(i =>
|
||||
isSameQuestionKey(getQuestionKey(i as anyObj), currentTitleId),
|
||||
@ -444,6 +447,17 @@ function extractUploadPath(res: any) {
|
||||
return res?.data?.filePath || res?.data?.url || res?.filePath || res?.url || res?.data?.path || ''
|
||||
}
|
||||
|
||||
function getUploadErrorMessage(error: unknown) {
|
||||
if (error instanceof Error) return error.message
|
||||
if (typeof error === 'string' && error) return error
|
||||
if (error === null || error === undefined) return '上传失败,未返回错误信息'
|
||||
try {
|
||||
return JSON.stringify(error)
|
||||
} catch {
|
||||
return '上传失败'
|
||||
}
|
||||
}
|
||||
|
||||
function findQuestionIndexById(answerId: unknown) {
|
||||
return paperList.value.findIndex(i => isSameQuestionKey(getQuestionKey(i as anyObj), answerId))
|
||||
}
|
||||
|
||||
@ -348,11 +348,21 @@ export const globalStore = defineStore('global', () => {
|
||||
}
|
||||
// PageSpy
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const PageSpy = window.PageSpy
|
||||
if (typeof PageSpy !== 'function') {
|
||||
console.warn('PageSpy is not ready')
|
||||
return
|
||||
}
|
||||
const { userInfo } = userStore()
|
||||
window.$pageSpy = new window.PageSpy({
|
||||
window.$pageSpy?.abort?.()
|
||||
window.$pageSpy = new PageSpy({
|
||||
title: `${userInfo ? `${userInfo.userId}-${userInfo.account}` : '未登陆'}`,
|
||||
project: getSN(),
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('PageSpy init failed', err)
|
||||
}
|
||||
}, 1000)
|
||||
return null
|
||||
} catch (err) {
|
||||
|
||||
@ -125,7 +125,7 @@ const pack = (method: string, config: anyObj = {}, cb?: anyFuc): any => {
|
||||
if (res.code === 200) {
|
||||
resolve(res.data)
|
||||
} else {
|
||||
reject(res.message)
|
||||
reject(new Error(res.message || `${method} failed: ${JSON.stringify(res)}`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +57,16 @@ const normalizeCosUrl = (location: string, sts: CosStsData, key: string) => {
|
||||
return /^https?:\/\//i.test(url) ? url.replace(/^http:\/\//i, 'https://') : `https://${url}`
|
||||
}
|
||||
|
||||
const normalizeUploadError = (error: unknown, fallback = 'COS上传失败') => {
|
||||
if (error instanceof Error) return error
|
||||
if (typeof error === 'string' && error) return new Error(error)
|
||||
try {
|
||||
return new Error(JSON.stringify(error) || fallback)
|
||||
} catch {
|
||||
return new Error(fallback)
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeStsTime = (value: number) => (value > 9999999999 ? Math.floor(value / 1000) : value)
|
||||
|
||||
const normalizeSts = (sts: CosStsData): CosStsData => ({
|
||||
@ -103,6 +113,37 @@ const createCos = (fileType: CosFileType) =>
|
||||
},
|
||||
})
|
||||
|
||||
const uploadFileToCosByPost = async (
|
||||
sts: CosStsData,
|
||||
key: string,
|
||||
file: File | Blob,
|
||||
onProgress?: UploadProgress,
|
||||
) => {
|
||||
const fields = await createPostObjectFields(sts, key)
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open('POST', `https://${getCosHost(sts)}`)
|
||||
xhr.upload.onprogress = event => {
|
||||
if (event.lengthComputable) {
|
||||
onProgress?.({ percent: Math.round((event.loaded / event.total) * 100) })
|
||||
}
|
||||
}
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
reject(new Error(`COS上传失败(${xhr.status})`))
|
||||
}
|
||||
xhr.onerror = () => reject(new Error('COS上传网络异常'))
|
||||
xhr.ontimeout = () => reject(new Error('COS上传超时'))
|
||||
const formData = new FormData()
|
||||
Object.entries(fields).forEach(([name, value]) => formData.append(name, value))
|
||||
formData.append('file', file)
|
||||
xhr.send(formData)
|
||||
})
|
||||
}
|
||||
|
||||
export const uploadFileToCos = async (
|
||||
file: File | Blob,
|
||||
options: { fileName?: string; fileType?: CosFileType; onProgress?: UploadProgress } = {},
|
||||
@ -110,6 +151,8 @@ export const uploadFileToCos = async (
|
||||
const fileType = options.fileType || 2
|
||||
const sts = await getCosSts(fileType)
|
||||
const key = buildKey(sts, file, options.fileName)
|
||||
let location = ''
|
||||
try {
|
||||
const result = await createCos(fileType).putObject({
|
||||
Bucket: sts.bucket,
|
||||
Region: sts.region,
|
||||
@ -120,7 +163,20 @@ export const uploadFileToCos = async (
|
||||
options.onProgress?.({ percent: Math.round((progress.percent || 0) * 100) })
|
||||
},
|
||||
})
|
||||
const url = normalizeCosUrl(result.Location, sts, key)
|
||||
location = result?.Location || ''
|
||||
if (!location) throw new Error('COS上传结果为空')
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'COS putObject failed, fallback to postObject',
|
||||
normalizeUploadError(error).message,
|
||||
)
|
||||
try {
|
||||
await uploadFileToCosByPost(sts, key, file, options.onProgress)
|
||||
} catch (postError) {
|
||||
throw normalizeUploadError(postError)
|
||||
}
|
||||
}
|
||||
const url = normalizeCosUrl(location, sts, key)
|
||||
return {
|
||||
bucket: sts.bucket,
|
||||
filePath: url,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user