feat: 新增物理弹球实验室及配套功能
1. 新增物理弹球游戏全套代码,包含物理引擎、规则、渲染、游戏逻辑、存档、音效、UI 2. 新增物理弹球相关调试与单元测试脚本 3. 优化私有剪贴上传功能:增加200MB上传限制与错误提示 4. 修复复古弹球桌的光滑面防粘附与休眠逻辑
This commit is contained in:
@@ -20,12 +20,32 @@ const state = {
|
||||
}
|
||||
}
|
||||
const isQuickEntryMode = () => state.entryMode === 'quick'
|
||||
const MAX_UPLOAD_BYTES = 200 * 1024 * 1024
|
||||
|
||||
const normalizeError = (error) => {
|
||||
const text = String(error && error.message ? error.message : error || '').trim()
|
||||
if (!text) return 'unknown_error'
|
||||
return text
|
||||
}
|
||||
const describeUploadError = (error, file) => {
|
||||
const code = normalizeError(error)
|
||||
const name = String(file && file.name || '').trim()
|
||||
const prefix = name ? `文件“${name}”` : '文件'
|
||||
if (code === 'unauthorized') return '上传失败:登录状态已失效,请重新登录'
|
||||
if (code === 'empty_file') return `${prefix}为空,未能上传`
|
||||
if (code === 'file_too_large' || code === 'upload_failed_413' || code === 'unexpected_response_413') {
|
||||
return `${prefix}过大,当前上限为 200MB`
|
||||
}
|
||||
if (code === 'upload_network_error') return '上传失败:网络中断或服务器无响应'
|
||||
if (code === 'upload_aborted') return '上传已中断,请重新上传'
|
||||
if (code === 'upload_failed_400') return '上传失败:请求无效,请重新选择文件后再试'
|
||||
if (code === 'upload_failed_401') return '上传失败:当前未登录或登录已过期'
|
||||
if (code === 'upload_failed_403') return '上传失败:当前没有权限执行此操作'
|
||||
if (code === 'upload_failed_404') return '上传失败:上传接口不存在或访问地址失效'
|
||||
if (code === 'upload_failed_415') return '上传失败:文件类型或请求格式不被服务器接受'
|
||||
if (code === 'upload_failed_500' || code === 'upload_failed') return '上传失败:服务器处理文件时出错'
|
||||
return `上传失败:${code}`
|
||||
}
|
||||
const folderDisplayName = (code) => {
|
||||
const folder = state.folders.find(item => item.code === code)
|
||||
return folder ? folder.name : '未命名目录'
|
||||
@@ -400,9 +420,14 @@ const uploadFiles = async (files, { uploadingText = '正在上传...', successMe
|
||||
try {
|
||||
let done = 0
|
||||
for (const file of list) {
|
||||
await uploadFile(file, groupId, (loaded, totalBytes) => {
|
||||
setProgress(true, done, list.length, { loaded, total: totalBytes })
|
||||
})
|
||||
try {
|
||||
await uploadFile(file, groupId, (loaded, totalBytes) => {
|
||||
setProgress(true, done, list.length, { loaded, total: totalBytes })
|
||||
})
|
||||
} catch (error) {
|
||||
if (error && typeof error === 'object' && !error.file) error.file = file
|
||||
throw error
|
||||
}
|
||||
done += 1
|
||||
setProgress(true, done, list.length)
|
||||
}
|
||||
@@ -755,6 +780,10 @@ const resolveFileName = (file) => {
|
||||
// 使用 XHR 上传,利用 upload.onprogress 获取字节级进度(fetch 不支持上传进度事件)
|
||||
const uploadFile = (file, groupId, onProgress) => new Promise((resolve, reject) => {
|
||||
if (!file) { resolve(); return }
|
||||
if (Number(file.size || 0) > MAX_UPLOAD_BYTES) {
|
||||
reject(new Error('file_too_large'))
|
||||
return
|
||||
}
|
||||
const name = resolveFileName(file)
|
||||
const groupPart = Number.isFinite(groupId) ? `&group_id=${encodeURIComponent(String(groupId))}` : ''
|
||||
const url = `${apiBase}/upload?filename=${encodeURIComponent(name)}&mime=${encodeURIComponent(file.type || 'application/octet-stream')}${groupPart}`
|
||||
@@ -851,7 +880,8 @@ const handlePaste = async (event) => {
|
||||
if (stringSaveError) throw stringSaveError
|
||||
await refreshCurrentView({ successMessage: '已保存到服务器' })
|
||||
} catch (e) {
|
||||
setStatus('保存失败', 'error')
|
||||
const files = items.filter(i => i.kind === 'file').map(i => i.getAsFile()).filter(Boolean)
|
||||
setStatus(files.length ? describeUploadError(e, e && e.file ? e.file : files[0]) : `保存失败:${normalizeError(e)}`, 'error')
|
||||
} finally {
|
||||
setProgress(false)
|
||||
}
|
||||
@@ -867,7 +897,7 @@ const handleDrop = async (event) => {
|
||||
try {
|
||||
await uploadFiles(files)
|
||||
} catch (e) {
|
||||
setStatus('上传失败', 'error')
|
||||
setStatus(describeUploadError(e, e && e.file ? e.file : files[0]), 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -980,7 +1010,7 @@ const bindDesktopClassicInputs = () => {
|
||||
await uploadFiles(files, { uploadingText })
|
||||
input.value = ''
|
||||
} catch (error) {
|
||||
setStatus('上传失败', 'error')
|
||||
setStatus(describeUploadError(error, error && error.file ? error.file : files[0]), 'error')
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1020,7 +1050,7 @@ const bindMobileInputs = () => {
|
||||
await uploadFiles(files)
|
||||
inputEl.value = ''
|
||||
} catch (e) {
|
||||
setStatus('上传失败', 'error')
|
||||
setStatus(describeUploadError(e, e && e.file ? e.file : files[0]), 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user