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 }> {
|
function handleSaveAsync(): Promise<{ blob: Blob | null; data: string | null }> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (signaturePad.isEmpty() && !hasBackground) {
|
if (signaturePad.isEmpty() && !hasBackground) {
|
||||||
@ -102,7 +113,17 @@ function handleSaveAsync(): Promise<{ blob: Blob | null; data: string | null }>
|
|||||||
}
|
}
|
||||||
const canvas = canvasRef.value as HTMLCanvasElement
|
const canvas = canvasRef.value as HTMLCanvasElement
|
||||||
const pointData = JSON.stringify(signaturePad.toData())
|
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
|
canvasRef.value.height = canvasPanelRef.value.clientHeight
|
||||||
signaturePad = new SignaturePad(canvasRef.value, {
|
signaturePad = new SignaturePad(canvasRef.value, {
|
||||||
backgroundColor: props.bg,
|
backgroundColor: props.bg,
|
||||||
throttle: 0,
|
throttle: 16,
|
||||||
|
minDistance: 1,
|
||||||
})
|
})
|
||||||
fillCanvasBackground()
|
fillCanvasBackground()
|
||||||
}
|
}
|
||||||
@ -203,9 +225,16 @@ defineExpose({
|
|||||||
.signature-pad {
|
.signature-pad {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
.canvas-panel {
|
.canvas-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
touch-action: none;
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { XMessage, debounce } from '@/hooks'
|
import { XMessage, debounce } from '@/hooks'
|
||||||
import { $XXL, isWebEvn } from '@/utils'
|
|
||||||
import CameraPreview from './CameraPreview.vue'
|
import CameraPreview from './CameraPreview.vue'
|
||||||
import ImageCropper from '@/components/ImageCropper/index.vue'
|
import ImageCropper from '@/components/ImageCropper/index.vue'
|
||||||
|
|
||||||
@ -63,9 +62,19 @@ async function save() {
|
|||||||
return XMessage.error('内容为空,无法保存')
|
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)
|
const showCropper = ref(false)
|
||||||
@ -78,6 +87,7 @@ function onUploadCamera(options: any) {
|
|||||||
showCanvas.value = false
|
showCanvas.value = false
|
||||||
uploadQueue.value.push(options)
|
uploadQueue.value.push(options)
|
||||||
processQueue()
|
processQueue()
|
||||||
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
function processQueue() {
|
function processQueue() {
|
||||||
@ -126,25 +136,7 @@ async function handleStart() {
|
|||||||
return XMessage.error('上传中,请稍后~')
|
return XMessage.error('上传中,请稍后~')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWebEvn) {
|
showCanvas.value = true
|
||||||
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()
|
const uploadRef = ref()
|
||||||
|
|||||||
@ -233,11 +233,11 @@ function setUploadLoadingByTitleId(titleId: unknown, value: boolean) {
|
|||||||
// 上传
|
// 上传
|
||||||
async function uploadFile(options: anyObj) {
|
async function uploadFile(options: anyObj) {
|
||||||
const currentQuestion = paperList.value[activeIdx.value] as anyObj | undefined
|
const currentQuestion = paperList.value[activeIdx.value] as anyObj | undefined
|
||||||
if (!currentQuestion) return
|
if (!currentQuestion) return false
|
||||||
const currentTitleId = getQuestionKey(currentQuestion)
|
const currentTitleId = getQuestionKey(currentQuestion)
|
||||||
if (currentQuestion.uploadLoading) {
|
if (currentQuestion.uploadLoading) {
|
||||||
XMessage.error('上传中,请稍后~')
|
XMessage.error('上传中,请稍后~')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
setUploadLoadingByTitleId(currentTitleId, true)
|
setUploadLoadingByTitleId(currentTitleId, true)
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ async function uploadFile(options: anyObj) {
|
|||||||
.trim()
|
.trim()
|
||||||
if ((options.submitAnswerPicOriginal || !options.isAndroid) && !path) {
|
if ((options.submitAnswerPicOriginal || !options.isAndroid) && !path) {
|
||||||
hud.error('上传成功但未获取到图片地址,请重试')
|
hud.error('上传成功但未获取到图片地址,请重试')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
if (options.submitAnswerPicOriginal) {
|
if (options.submitAnswerPicOriginal) {
|
||||||
submitQuestion({
|
submitQuestion({
|
||||||
@ -298,13 +298,16 @@ async function uploadFile(options: anyObj) {
|
|||||||
await switchQuestion(activeIdx.value)
|
await switchQuestion(activeIdx.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('uploadFile error', error)
|
const message = getUploadErrorMessage(error)
|
||||||
|
console.error('uploadFile error', message, error)
|
||||||
hud.error(
|
hud.error(
|
||||||
error instanceof Error && error.message === 'upload timeout'
|
error instanceof Error && error.message === 'upload timeout'
|
||||||
? '上传超时,请重试~'
|
? '上传超时,请重试~'
|
||||||
: '网络异常,请检查网络~',
|
: '网络异常,请检查网络~',
|
||||||
)
|
)
|
||||||
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
const finalIdx = paperList.value.findIndex(i =>
|
const finalIdx = paperList.value.findIndex(i =>
|
||||||
isSameQuestionKey(getQuestionKey(i as anyObj), currentTitleId),
|
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 || ''
|
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) {
|
function findQuestionIndexById(answerId: unknown) {
|
||||||
return paperList.value.findIndex(i => isSameQuestionKey(getQuestionKey(i as anyObj), answerId))
|
return paperList.value.findIndex(i => isSameQuestionKey(getQuestionKey(i as anyObj), answerId))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -348,11 +348,21 @@ export const globalStore = defineStore('global', () => {
|
|||||||
}
|
}
|
||||||
// PageSpy
|
// PageSpy
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const { userInfo } = userStore()
|
try {
|
||||||
window.$pageSpy = new window.PageSpy({
|
const PageSpy = window.PageSpy
|
||||||
title: `${userInfo ? `${userInfo.userId}-${userInfo.account}` : '未登陆'}`,
|
if (typeof PageSpy !== 'function') {
|
||||||
project: getSN(),
|
console.warn('PageSpy is not ready')
|
||||||
})
|
return
|
||||||
|
}
|
||||||
|
const { userInfo } = userStore()
|
||||||
|
window.$pageSpy?.abort?.()
|
||||||
|
window.$pageSpy = new PageSpy({
|
||||||
|
title: `${userInfo ? `${userInfo.userId}-${userInfo.account}` : '未登陆'}`,
|
||||||
|
project: getSN(),
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('PageSpy init failed', err)
|
||||||
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
return null
|
return null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -125,7 +125,7 @@ const pack = (method: string, config: anyObj = {}, cb?: anyFuc): any => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
} else {
|
} 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}`
|
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 normalizeStsTime = (value: number) => (value > 9999999999 ? Math.floor(value / 1000) : value)
|
||||||
|
|
||||||
const normalizeSts = (sts: CosStsData): CosStsData => ({
|
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 (
|
export const uploadFileToCos = async (
|
||||||
file: File | Blob,
|
file: File | Blob,
|
||||||
options: { fileName?: string; fileType?: CosFileType; onProgress?: UploadProgress } = {},
|
options: { fileName?: string; fileType?: CosFileType; onProgress?: UploadProgress } = {},
|
||||||
@ -110,17 +151,32 @@ export const uploadFileToCos = async (
|
|||||||
const fileType = options.fileType || 2
|
const fileType = options.fileType || 2
|
||||||
const sts = await getCosSts(fileType)
|
const sts = await getCosSts(fileType)
|
||||||
const key = buildKey(sts, file, options.fileName)
|
const key = buildKey(sts, file, options.fileName)
|
||||||
const result = await createCos(fileType).putObject({
|
let location = ''
|
||||||
Bucket: sts.bucket,
|
try {
|
||||||
Region: sts.region,
|
const result = await createCos(fileType).putObject({
|
||||||
Key: key,
|
Bucket: sts.bucket,
|
||||||
Body: file,
|
Region: sts.region,
|
||||||
ContentType: file.type || undefined,
|
Key: key,
|
||||||
onProgress: progress => {
|
Body: file,
|
||||||
options.onProgress?.({ percent: Math.round((progress.percent || 0) * 100) })
|
ContentType: file.type || undefined,
|
||||||
},
|
onProgress: progress => {
|
||||||
})
|
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 {
|
return {
|
||||||
bucket: sts.bucket,
|
bucket: sts.bucket,
|
||||||
filePath: url,
|
filePath: url,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user