Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
462d9863
提交
462d9863
authored
8月 29, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 添加文件上传进度显示,优化上传逻辑
上级
02ee8d01
隐藏空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
207 行增加
和
79 行删除
+207
-79
upload-progress.test.ts
...tor/wangeditor-customer/__tests__/upload-progress.test.ts
+83
-0
AudioDialog.jsx
...editor/wangeditor-customer/features/audio/AudioDialog.jsx
+1
-1
ImageDialog.jsx
...editor/wangeditor-customer/features/image/ImageDialog.jsx
+47
-38
VideoDialog.jsx
...editor/wangeditor-customer/features/video/VideoDialog.jsx
+42
-36
oss.js
apps/admin/src/utils/oss.js
+34
-4
没有找到文件。
apps/admin/src/components/editor/wangeditor-customer/__tests__/upload-progress.test.ts
0 → 100644
浏览文件 @
462d9863
import
{
beforeEach
,
describe
,
expect
,
it
,
vi
}
from
'vitest'
const
{
client
}
=
vi
.
hoisted
(()
=>
({
client
:
{
generateObjectUrl
:
vi
.
fn
(),
multipartUpload
:
vi
.
fn
(),
put
:
vi
.
fn
(),
},
}))
vi
.
mock
(
'ali-oss'
,
()
=>
({
default
:
vi
.
fn
(
function
OSSClient
()
{
return
client
}),
}))
vi
.
mock
(
'@/api/base'
,
()
=>
({
getSTSToken
:
vi
.
fn
().
mockResolvedValue
({
data
:
{
endpoint
:
'https://oss-cn-beijing.aliyuncs.com'
,
AccessKeyId
:
'test-id'
,
AccessKeySecret
:
'test-secret'
,
SecurityToken
:
'test-token'
,
},
}),
}))
import
{
uploadFile
}
from
'@/utils/oss'
describe
(
'OSS upload progress'
,
()
=>
{
beforeEach
(()
=>
{
vi
.
clearAllMocks
()
})
it
(
'keeps the existing single-request upload when progress is not requested'
,
async
()
=>
{
client
.
put
.
mockResolvedValue
({
url
:
'https://example.com/image.jpg'
})
await
expect
(
uploadFile
({
name
:
'image.jpg'
})).
resolves
.
toBe
(
'https://example.com/image.jpg'
)
expect
(
client
.
put
).
toHaveBeenCalledOnce
()
expect
(
client
.
multipartUpload
).
not
.
toHaveBeenCalled
()
})
it
(
'reports multipart progress as an integer percentage'
,
async
()
=>
{
client
.
multipartUpload
.
mockImplementation
(
async
(
name
,
file
,
options
)
=>
{
await
options
.
progress
(
0
)
await
options
.
progress
(
0.42
)
await
options
.
progress
(
1
)
return
{
name
}
})
client
.
generateObjectUrl
.
mockReturnValue
(
'https://example.com/video.mp4'
)
const
onProgress
=
vi
.
fn
()
await
expect
(
uploadFile
({
name
:
'video.mp4'
},
{
onProgress
})).
resolves
.
toBe
(
'https://example.com/video.mp4'
,
)
expect
(
onProgress
.
mock
.
calls
.
map
(([
percent
])
=>
percent
)).
toEqual
([
0
,
42
,
100
])
expect
(
client
.
multipartUpload
).
toHaveBeenCalledOnce
()
expect
(
client
.
put
).
not
.
toHaveBeenCalled
()
})
it
(
'resumes a failed multipart upload from its latest checkpoint'
,
async
()
=>
{
const
checkpoint
=
{
name
:
'video.mp4'
,
doneParts
:
[{
number
:
1
}]
}
client
.
multipartUpload
.
mockImplementationOnce
(
async
(
name
,
file
,
options
)
=>
{
await
options
.
progress
(
0.5
,
checkpoint
)
throw
new
Error
(
'network error'
)
})
.
mockImplementationOnce
(
async
(
name
,
file
,
options
)
=>
{
await
options
.
progress
(
1
,
null
)
return
{
name
}
})
client
.
generateObjectUrl
.
mockReturnValue
(
'https://example.com/video.mp4'
)
const
onProgress
=
vi
.
fn
()
await
expect
(
uploadFile
({
name
:
'video.mp4'
},
{
onProgress
,
parallel
:
3
,
retries
:
3
}),
).
resolves
.
toBe
(
'https://example.com/video.mp4'
)
expect
(
client
.
multipartUpload
).
toHaveBeenCalledTimes
(
2
)
expect
(
client
.
multipartUpload
.
mock
.
calls
[
0
][
2
]).
toMatchObject
({
parallel
:
3
})
expect
(
client
.
multipartUpload
.
mock
.
calls
[
1
][
2
]).
toMatchObject
({
checkpoint
,
parallel
:
3
})
expect
(
onProgress
.
mock
.
calls
.
map
(([
percent
])
=>
percent
)).
toEqual
([
50
,
100
])
})
})
apps/admin/src/components/editor/wangeditor-customer/features/audio/AudioDialog.jsx
浏览文件 @
462d9863
...
@@ -106,7 +106,7 @@ const AudioModal = ({ open, onClose, payload = {}, runtime }) => {
...
@@ -106,7 +106,7 @@ const AudioModal = ({ open, onClose, payload = {}, runtime }) => {
<
div
className=
'editor-content-form'
>
<
div
className=
'editor-content-form'
>
<
Form
layout=
'vertical'
name=
'validate_other'
form=
{
form
}
onFinish=
{
onFinish
}
>
<
Form
layout=
'vertical'
name=
'validate_other'
form=
{
form
}
onFinish=
{
onFinish
}
>
<
Form
.
Item
label=
'上传音频'
name=
'audioUrl'
>
<
Form
.
Item
label=
'上传音频'
name=
'audioUrl'
>
<
Spin
spinning=
{
uploading
}
tip
=
'上传中'
>
<
Spin
spinning=
{
uploading
}
description
=
'上传中'
>
<
div
className=
'editor-dragger'
>
<
div
className=
'editor-dragger'
>
<
Dragger
{
...
uploadProps
}
showUploadList=
{
false
}
>
<
Dragger
{
...
uploadProps
}
showUploadList=
{
false
}
>
{
!
audioUrl
&&
(
{
!
audioUrl
&&
(
...
...
apps/admin/src/components/editor/wangeditor-customer/features/image/ImageDialog.jsx
浏览文件 @
462d9863
import
{
useState
,
useEffect
,
forwardRef
}
from
'react'
import
{
useState
,
useEffect
,
forwardRef
}
from
'react'
import
{
Divider
,
Upload
,
Input
,
Space
,
Button
,
Form
,
Spin
,
message
}
from
'antd'
import
{
Divider
,
Upload
,
Input
,
Space
,
Button
,
Form
,
Progress
,
message
}
from
'antd'
import
{
CloudUploadOutlined
}
from
'@ant-design/icons'
import
{
CloudUploadOutlined
}
from
'@ant-design/icons'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
{
DomEditor
,
SlateTransforms
}
from
'@wangeditor/editor'
import
'../../index.less'
import
'../../index.less'
...
@@ -20,6 +20,7 @@ const ImageModal = (props, ref) => {
...
@@ -20,6 +20,7 @@ const ImageModal = (props, ref) => {
const
fileAccept
=
[
'.png'
,
'.jpg'
,
'.jpeg'
,
'.svg'
]
const
fileAccept
=
[
'.png'
,
'.jpg'
,
'.jpeg'
,
'.svg'
]
const
[
form
]
=
Form
.
useForm
()
const
[
form
]
=
Form
.
useForm
()
const
[
uploading
,
setUploading
]
=
useState
(
false
)
// 文件上传状态
const
[
uploading
,
setUploading
]
=
useState
(
false
)
// 文件上传状态
const
[
uploadProgress
,
setUploadProgress
]
=
useState
(
0
)
const
[
initValues
,
setInitValues
]
=
useState
({
imgUrl
:
''
,
imgTitle
:
''
,
imgDescript
:
''
})
const
[
initValues
,
setInitValues
]
=
useState
({
imgUrl
:
''
,
imgTitle
:
''
,
imgDescript
:
''
})
const
[
tempNodeInfo
,
setTempNodeInfo
]
=
useState
(
null
)
const
[
tempNodeInfo
,
setTempNodeInfo
]
=
useState
(
null
)
...
@@ -47,14 +48,21 @@ const ImageModal = (props, ref) => {
...
@@ -47,14 +48,21 @@ const ImageModal = (props, ref) => {
}
}
return
true
return
true
},
},
customRequest
:
async
({
file
,
onSuccess
,
onError
})
=>
{
customRequest
:
async
({
file
,
on
Progress
,
on
Success
,
onError
})
=>
{
setUploading
(
true
)
setUploading
(
true
)
setUploadProgress
(
0
)
try
{
try
{
const
url
=
await
uploadFile
(
file
)
const
url
=
await
uploadFile
(
file
,
{
onProgress
:
(
percent
)
=>
{
setUploadProgress
(
percent
)
onProgress
?.({
percent
})
},
})
setImgUrl
(
url
)
setImgUrl
(
url
)
form
.
setFieldsValue
({
imgUrl
:
url
})
form
.
setFieldsValue
({
imgUrl
:
url
})
onSuccess
?.({
url
})
onSuccess
?.({
url
})
}
catch
(
error
)
{
}
catch
(
error
)
{
setUploadProgress
(
0
)
message
.
error
(
'图片上传失败,请重试'
)
message
.
error
(
'图片上传失败,请重试'
)
onError
?.(
error
instanceof
Error
?
error
:
new
Error
(
'图片上传失败'
))
onError
?.(
error
instanceof
Error
?
error
:
new
Error
(
'图片上传失败'
))
}
finally
{
}
finally
{
...
@@ -66,6 +74,7 @@ const ImageModal = (props, ref) => {
...
@@ -66,6 +74,7 @@ const ImageModal = (props, ref) => {
const
clear
=
()
=>
{
const
clear
=
()
=>
{
setImgUrl
(
''
)
setImgUrl
(
''
)
setUploading
(
false
)
setUploading
(
false
)
setUploadProgress
(
0
)
setTempNodeInfo
(
null
)
setTempNodeInfo
(
null
)
setInitValues
({
imgUrl
:
imgUrl
,
imgTitle
:
''
,
imgDescript
:
''
})
setInitValues
({
imgUrl
:
imgUrl
,
imgTitle
:
''
,
imgDescript
:
''
})
form
.
resetFields
()
form
.
resetFields
()
...
@@ -161,43 +170,43 @@ const ImageModal = (props, ref) => {
...
@@ -161,43 +170,43 @@ const ImageModal = (props, ref) => {
name=
'imgUrl'
name=
'imgUrl'
rules=
{
[{
required
:
true
,
message
:
'请上传图片'
}]
}
rules=
{
[{
required
:
true
,
message
:
'请上传图片'
}]
}
>
>
<
Spin
spinning=
{
uploading
}
>
<
div
className=
'editor-dragger'
>
<
div
className=
'editor-dragger'
>
<
Dragger
{
...
uploadProps
}
disabled=
{
uploading
}
showUploadList=
{
false
}
>
<
Dragger
{
...
uploadProps
}
showUploadList=
{
false
}
>
{
uploading
?
(
{
!
imgUrl
&&
(
<
div
className=
'editor-uploader-progress'
>
<
div
className=
'editor-uploader-process'
>
<
Progress
percent=
{
uploadProgress
}
status=
'active'
/>
<
p
className=
'ant-upload-drag-icon'
>
<
p
className=
'ant-upload-hint'
>
图片上传中,请勿关闭窗口
</
p
>
<
CloudUploadOutlined
style=
{
{
fontSize
:
40
}
}
/>
</
div
>
</
p
>
)
:
!
imgUrl
?
(
<
p
className=
'ant-upload-text'
>
点击上传或拖拽到此处上传
</
p
>
<
div
className=
'editor-uploader-process'
>
<
p
className=
'ant-upload-hint'
>
<
p
className=
'ant-upload-drag-icon'
>
支持上传 .png、.jpg、.jpeg、.svg格式的图片
<
CloudUploadOutlined
style=
{
{
fontSize
:
40
}
}
/>
</
p
>
</
p
>
<
p
className=
'ant-upload-text'
>
点击上传或拖拽到此处上传
</
p
>
<
p
className=
'ant-upload-hint'
>
支持上传 .png、.jpg、.jpeg、.svg格式的图片
</
p
>
</
div
>
)
:
(
<
div
className=
'editor-uploader-result'
>
<
div
className=
'editor-uploader-result-img'
>
<
img
src=
{
imgUrl
}
alt=
''
/>
</
div
>
</
div
>
)
}
<
div
className=
'editor-uploader-result-tips'
>
{
imgUrl
&&
(
<
Button
<
div
className=
'editor-uploader-result'
>
size=
'small'
<
div
className=
'editor-uploader-result-img'
>
color=
'primary'
<
img
src=
{
imgUrl
}
alt=
''
/>
variant=
'outlined'
</
div
>
onClick=
{
()
=>
{
<
div
className=
'editor-uploader-result-tips'
>
setImgUrl
(
null
)
<
Button
form
.
setFieldsValue
({
imgUrl
:
''
})
size=
'small'
}
}
color=
'primary'
>
variant=
'outlined'
替换
onClick=
{
()
=>
{
</
Button
>
setImgUrl
(
null
)
form
.
setFieldsValue
({
imgUrl
:
''
})
}
}
>
替换
</
Button
>
</
div
>
</
div
>
</
div
>
)
}
</
div
>
</
Dragger
>
)
}
</
div
>
</
Dragger
>
</
Spin
>
</
div
>
</
Form
.
Item
>
</
Form
.
Item
>
)
}
)
}
<
Form
.
Item
label=
'图片标题'
name=
'imgTitle'
extra=
'最多输入100字'
>
<
Form
.
Item
label=
'图片标题'
name=
'imgTitle'
extra=
'最多输入100字'
>
...
...
apps/admin/src/components/editor/wangeditor-customer/features/video/VideoDialog.jsx
浏览文件 @
462d9863
import
{
useState
,
useEffect
}
from
'react'
import
{
useState
,
useEffect
}
from
'react'
import
{
Modal
,
Divider
,
Upload
,
Input
,
Space
,
Button
,
Form
,
Spin
,
message
}
from
'antd'
import
{
Modal
,
Divider
,
Upload
,
Input
,
Space
,
Button
,
Form
,
Progress
,
message
}
from
'antd'
import
{
CloudUploadOutlined
}
from
'@ant-design/icons'
import
{
CloudUploadOutlined
}
from
'@ant-design/icons'
import
{
SlateTransforms
}
from
'@wangeditor/editor'
import
{
SlateTransforms
}
from
'@wangeditor/editor'
import
'../../index.less'
import
'../../index.less'
...
@@ -20,6 +20,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
...
@@ -20,6 +20,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
const
fileAccept
=
[
'.ogg'
,
'.mp4'
,
'.webm'
]
const
fileAccept
=
[
'.ogg'
,
'.mp4'
,
'.webm'
]
const
[
form
]
=
Form
.
useForm
()
const
[
form
]
=
Form
.
useForm
()
const
[
uploading
,
setUploading
]
=
useState
(
false
)
// 文件上传状态
const
[
uploading
,
setUploading
]
=
useState
(
false
)
// 文件上传状态
const
[
uploadProgress
,
setUploadProgress
]
=
useState
(
0
)
const
[
videoUrl
,
setVideoUrl
]
=
useState
(
''
)
const
[
videoUrl
,
setVideoUrl
]
=
useState
(
''
)
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -44,14 +45,23 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
...
@@ -44,14 +45,23 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
}
}
return
true
return
true
},
},
customRequest
:
async
({
file
,
onSuccess
,
onError
})
=>
{
customRequest
:
async
({
file
,
on
Progress
,
on
Success
,
onError
})
=>
{
setUploading
(
true
)
setUploading
(
true
)
setUploadProgress
(
0
)
try
{
try
{
const
url
=
await
uploadFile
(
file
)
const
url
=
await
uploadFile
(
file
,
{
parallel
:
3
,
retries
:
3
,
onProgress
:
(
percent
)
=>
{
setUploadProgress
(
percent
)
onProgress
?.({
percent
})
},
})
setVideoUrl
(
url
)
setVideoUrl
(
url
)
form
.
setFieldsValue
({
videoUrl
:
url
})
form
.
setFieldsValue
({
videoUrl
:
url
})
onSuccess
?.({
url
})
onSuccess
?.({
url
})
}
catch
(
error
)
{
}
catch
(
error
)
{
setUploadProgress
(
0
)
message
.
error
(
'视频上传失败,请重试'
)
message
.
error
(
'视频上传失败,请重试'
)
onError
?.(
error
instanceof
Error
?
error
:
new
Error
(
'视频上传失败'
))
onError
?.(
error
instanceof
Error
?
error
:
new
Error
(
'视频上传失败'
))
}
finally
{
}
finally
{
...
@@ -84,6 +94,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
...
@@ -84,6 +94,7 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
setVideoUrl
(
''
)
setVideoUrl
(
''
)
setUploading
(
false
)
setUploading
(
false
)
setUploadProgress
(
0
)
form
.
resetFields
()
form
.
resetFields
()
onClose
()
onClose
()
}
}
...
@@ -112,40 +123,35 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
...
@@ -112,40 +123,35 @@ const VideoModal = ({ open, onClose, payload = {}, runtime }) => {
name=
'videoUrl'
name=
'videoUrl'
rules=
{
[{
required
:
true
,
message
:
'请上传视频'
}]
}
rules=
{
[{
required
:
true
,
message
:
'请上传视频'
}]
}
>
>
<
Spin
spinning=
{
uploading
}
tip=
'上传中'
>
<
div
className=
'editor-dragger'
>
<
div
className=
'editor-dragger'
>
<
Dragger
{
...
uploadProps
}
disabled=
{
uploading
}
showUploadList=
{
false
}
>
<
Dragger
{
...
uploadProps
}
showUploadList=
{
false
}
>
{
uploading
?
(
{
!
videoUrl
&&
(
<
div
className=
'editor-uploader-progress'
>
<
div
className=
'editor-uploader-process'
>
<
Progress
percent=
{
uploadProgress
}
status=
'active'
/>
<
p
className=
'ant-upload-drag-icon'
>
<
p
className=
'ant-upload-hint'
>
视频上传中,请勿关闭窗口
</
p
>
<
CloudUploadOutlined
style=
{
{
fontSize
:
40
}
}
/>
</
div
>
</
p
>
)
:
!
videoUrl
?
(
<
p
className=
'ant-upload-text'
>
点击上传或拖拽到此处上传
</
p
>
<
div
className=
'editor-uploader-process'
>
<
p
className=
'ant-upload-hint'
>
支持上传 .ogg、.mp4、.webm 格式的文件
</
p
>
<
p
className=
'ant-upload-drag-icon'
>
<
CloudUploadOutlined
style=
{
{
fontSize
:
40
}
}
/>
</
p
>
<
p
className=
'ant-upload-text'
>
点击上传或拖拽到此处上传
</
p
>
<
p
className=
'ant-upload-hint'
>
支持上传 .ogg、.mp4、.webm 格式的文件
</
p
>
</
div
>
)
:
(
<
div
className=
'editor-uploader-result'
>
<
div
className=
'editor-uploader-result-img'
>
<
video
src=
{
videoUrl
}
autoPlay
height=
'110'
width=
'200'
alt=
''
/>
</
div
>
</
div
>
)
}
<
div
className=
'editor-uploader-result-tips'
>
{
videoUrl
&&
(
<
Button
size=
'small'
type=
'primary'
ghost
onClick=
{
()
=>
setVideoUrl
(
null
)
}
>
<>
替换
<
div
className=
'editor-uploader-result'
>
</
Button
>
<
div
className=
'editor-uploader-result-img'
>
</
div
>
<
video
src=
{
videoUrl
}
autoPlay
height=
'110'
width=
'200'
alt=
''
/>
</
div
>
</
div
>
)
}
<
div
className=
'editor-uploader-result-tips'
>
</
Dragger
>
<
Button
</
div
>
size=
'small'
type=
'primary'
ghost
onClick=
{
()
=>
setVideoUrl
(
null
)
}
>
替换
</
Button
>
</
div
>
</
div
>
</>
)
}
</
Dragger
>
</
div
>
</
Spin
>
</
Form
.
Item
>
</
Form
.
Item
>
<
Form
.
Item
<
Form
.
Item
label=
'视频标题'
label=
'视频标题'
...
...
apps/admin/src/utils/oss.js
浏览文件 @
462d9863
...
@@ -23,7 +23,7 @@ export async function getOSSClient(bucket = 'zxts-book-file') {
...
@@ -23,7 +23,7 @@ export async function getOSSClient(bucket = 'zxts-book-file') {
const
client
=
new
OSS
({
const
client
=
new
OSS
({
bucket
,
bucket
,
...
config
,
...
config
,
timeout
:
18
0000
,
timeout
:
60
0000
,
refreshSTSToken
:
async
()
=>
{
refreshSTSToken
:
async
()
=>
{
const
{
accessKeyId
,
accessKeySecret
,
stsToken
}
=
await
getOSSConfig
()
const
{
accessKeyId
,
accessKeySecret
,
stsToken
}
=
await
getOSSConfig
()
return
{
accessKeyId
,
accessKeySecret
,
stsToken
}
return
{
accessKeyId
,
accessKeySecret
,
stsToken
}
...
@@ -38,17 +38,47 @@ function getExtension(file) {
...
@@ -38,17 +38,47 @@ function getExtension(file) {
const
name
=
typeof
file
.
name
===
'string'
?
file
.
name
:
''
const
name
=
typeof
file
.
name
===
'string'
?
file
.
name
:
''
const
fromName
=
name
.
includes
(
'.'
)
?
`.
${
name
.
split
(
'.'
).
pop
().
toLowerCase
()}
`
:
''
const
fromName
=
name
.
includes
(
'.'
)
?
`.
${
name
.
split
(
'.'
).
pop
().
toLowerCase
()}
`
:
''
if
(
fromName
)
return
fromName
if
(
fromName
)
return
fromName
return
{
'image/jpeg'
:
'.jpg'
,
'image/png'
:
'.png'
,
'video/mp4'
:
'.mp4'
}[(
file
.
type
||
''
).
toLowerCase
()]
||
'.bin'
return
(
{
'image/jpeg'
:
'.jpg'
,
'image/png'
:
'.png'
,
'video/mp4'
:
'.mp4'
}[
(
file
.
type
||
''
).
toLowerCase
()
]
||
'.bin'
)
}
}
function
buildObjectName
(
file
,
{
pathPrefix
=
dayjs
().
format
(
'YYYY/MM/DD'
),
filenamePrefix
=
'file'
}
=
{})
{
function
buildObjectName
(
file
,
{
pathPrefix
=
dayjs
().
format
(
'YYYY/MM/DD'
),
filenamePrefix
=
'file'
}
=
{},
)
{
const
filename
=
`
${
filenamePrefix
}
-
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
slice
(
2
)}${
getExtension
(
file
)}
`
const
filename
=
`
${
filenamePrefix
}
-
${
Date
.
now
()}
-
${
Math
.
random
().
toString
(
36
).
slice
(
2
)}${
getExtension
(
file
)}
`
return
pathPrefix
?
`
${
pathPrefix
.
replace
(
/
\/
$/
,
''
)}
/
${
filename
}
`
:
filename
return
pathPrefix
?
`
${
pathPrefix
.
replace
(
/
\/
$/
,
''
)}
/
${
filename
}
`
:
filename
}
}
export
async
function
uploadFile
(
file
,
options
=
{})
{
export
async
function
uploadFile
(
file
,
options
=
{})
{
const
oss
=
await
getOSSClient
(
options
.
bucket
)
const
oss
=
await
getOSSClient
(
options
.
bucket
)
const
result
=
await
oss
.
put
(
buildObjectName
(
file
,
options
),
file
)
const
objectName
=
buildObjectName
(
file
,
options
)
if
(
options
.
onProgress
)
{
const
retries
=
options
.
retries
??
0
let
checkpoint
for
(
let
attempt
=
0
;
attempt
<=
retries
;
attempt
+=
1
)
{
try
{
const
result
=
await
oss
.
multipartUpload
(
objectName
,
file
,
{
checkpoint
,
parallel
:
options
.
parallel
,
progress
:
(
percentage
,
nextCheckpoint
)
=>
{
checkpoint
=
nextCheckpoint
||
checkpoint
options
.
onProgress
(
Math
.
round
(
percentage
*
100
))
},
})
return
oss
.
generateObjectUrl
(
result
.
name
)
}
catch
(
error
)
{
if
(
attempt
===
retries
)
throw
error
}
}
}
const
result
=
await
oss
.
put
(
objectName
,
file
)
return
result
.
url
return
result
.
url
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论