Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
AI_BOOK_TOOL
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
template admin
AI_BOOK_TOOL
Commits
2c70614c
Commit
2c70614c
authored
Jul 03, 2025
by
liujiangnan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 支持图片批量上传的方式
parent
7cde8ab7
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
448 additions
and
23 deletions
+448
-23
index.css
form/index.css
+215
-11
index.html
form/index.html
+76
-2
index.js
form/index.js
+157
-10
No files found.
form/index.css
View file @
2c70614c
html
,
body
{
html
,
body
{
height
:
100%
;
width
:
100%
;
margin
:
0
;
margin
:
0
;
padding
:
0
;
padding
:
0
;
height
:
100%
;
background
:
#f6f7fa
;
overflow
:
hidden
;
}
}
#app
{
#app
{
height
:
100vh
;
height
:
100vh
;
width
:
100vw
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
}
.el-container
{
.el-container
{
...
@@ -779,21 +784,29 @@ html, body {
...
@@ -779,21 +784,29 @@ html, body {
/* 上传界面样式 */
/* 上传界面样式 */
.upload-area
{
.upload-area
{
width
:
800px
;
width
:
95vw
;
background-color
:
#fff
;
height
:
95vh
;
padding
:
30px
;
min-width
:
320px
;
border-radius
:
8px
;
min-height
:
480px
;
box-shadow
:
0
2px
12px
0
rgba
(
0
,
0
,
0
,
0.1
);
background
:
#fff
;
border-radius
:
16px
;
box-shadow
:
0
6px
32px
0
rgba
(
0
,
0
,
0
,
0.10
);
display
:
flex
;
flex-direction
:
column
;
padding
:
2vw
2vw
;
box-sizing
:
border-box
;
overflow
:
auto
;
margin
:
0
;
}
}
.upload-section
{
.upload-section
{
margin-bottom
:
30px
;
margin-bottom
:
2vw
;
}
}
.upload-section
h3
{
.upload-section
h3
{
margin
:
0
0
20px
0
;
margin
:
0
0
1vw
0
;
color
:
#303133
;
color
:
#303133
;
font-size
:
1
8px
;
font-size
:
1
.3vw
;
}
}
.upload-box
{
.upload-box
{
...
@@ -841,7 +854,7 @@ html, body {
...
@@ -841,7 +854,7 @@ html, body {
.upload-actions
{
.upload-actions
{
text-align
:
center
;
text-align
:
center
;
margin-top
:
30px
;
margin-top
:
2vw
;
}
}
/* 时间轴样式 */
/* 时间轴样式 */
...
@@ -961,4 +974,195 @@ html, body {
...
@@ -961,4 +974,195 @@ html, body {
.el-timeline-item__node--info
{
.el-timeline-item__node--info
{
background-color
:
#909399
;
background-color
:
#909399
;
}
/* 图片列表样式 */
.image-list
{
margin-top
:
20px
;
padding
:
15px
;
border
:
1px
solid
#e4e7ed
;
border-radius
:
4px
;
background-color
:
#fafafa
;
}
.image-list
h4
{
margin
:
0
0
15px
0
;
color
:
#303133
;
font-size
:
14px
;
font-weight
:
500
;
}
.image-grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
180px
,
1
fr
));
gap
:
1.2vw
;
max-height
:
40vh
;
overflow-y
:
auto
;
}
.image-item
{
position
:
relative
;
border
:
1px
solid
#e4e7ed
;
border-radius
:
6px
;
background
:
white
;
overflow
:
hidden
;
cursor
:
move
;
transition
:
all
0.3s
ease
;
min-width
:
0
;
max-width
:
100%
;
}
.image-item
:hover
{
border-color
:
#409eff
;
box-shadow
:
0
2px
8px
rgba
(
64
,
158
,
255
,
0.2
);
}
.image-item.dragging
{
opacity
:
0.5
;
transform
:
rotate
(
5deg
);
}
.image-preview
{
position
:
relative
;
width
:
100%
;
height
:
8vw
;
min-height
:
60px
;
max-height
:
160px
;
overflow
:
hidden
;
}
.image-preview
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
cover
;
display
:
block
;
}
.image-overlay
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
opacity
:
0
;
transition
:
opacity
0.3s
ease
;
}
.image-item
:hover
.image-overlay
{
opacity
:
1
;
}
.remove-btn
{
background
:
rgba
(
245
,
108
,
108
,
0.9
)
!important
;
border
:
none
!important
;
}
.remove-btn
:hover
{
background
:
#f56c6c
!important
;
}
.image-info
{
padding
:
8px
;
}
.image-name
{
font-size
:
16px
;
color
:
#303133
;
margin-bottom
:
0.3vw
;
word-break
:
break-all
;
line-height
:
1.3
;
white-space
:
normal
;
overflow
:
visible
;
display
:
block
;
}
.image-size
{
font-size
:
11px
;
color
:
#909399
;
}
.image-item
.el-progress
{
margin-top
:
8px
;
}
/* 拖拽时的样式 */
.image-item
[
draggable
=
"true"
]
:active
{
opacity
:
0.7
;
}
.image-grid
::-webkit-scrollbar
{
width
:
6px
;
}
.image-grid
::-webkit-scrollbar-thumb
{
background-color
:
#c0c4cc
;
border-radius
:
3px
;
}
.image-grid
::-webkit-scrollbar-track
{
background-color
:
#f5f7fa
;
border-radius
:
3px
;
}
/* 类型选择样式 */
.type-select-container
{
padding
:
20px
;
background-color
:
#f5f7fa
;
border-radius
:
4px
;
text-align
:
center
;
}
.type-select-container
.el-radio-group
{
display
:
flex
;
justify-content
:
center
;
gap
:
30px
;
}
.type-select-container
.el-radio
{
margin-right
:
0
;
padding
:
10px
20px
;
border
:
1px
solid
#dcdfe6
;
border-radius
:
4px
;
background-color
:
white
;
transition
:
all
0.3s
ease
;
}
.type-select-container
.el-radio
:hover
{
border-color
:
#409eff
;
background-color
:
#f0f9ff
;
}
.type-select-container
.el-radio.is-checked
{
border-color
:
#409eff
;
background-color
:
#ecf5ff
;
}
.type-select-container
.el-radio__label
{
font-size
:
14px
;
font-weight
:
500
;
color
:
#303133
;
}
@media
(
max-width
:
900px
)
{
.upload-area
{
width
:
99vw
;
height
:
99vh
;
padding
:
2vw
1vw
;
border-radius
:
0
;
box-shadow
:
none
;
}
.image-grid
{
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
120px
,
1
fr
));
gap
:
8px
;
}
.image-preview
{
height
:
60px
;
}
.image-name
{
font-size
:
12px
;
}
}
}
\ No newline at end of file
form/index.html
View file @
2c70614c
...
@@ -62,7 +62,7 @@
...
@@ -62,7 +62,7 @@
<!-- 上传界面 -->
<!-- 上传界面 -->
<div
class=
"upload-area"
v-else
>
<div
class=
"upload-area"
v-else
>
<div
class=
"upload-section"
>
<div
class=
"upload-section"
>
<
h3>
音频文件上传
</h3
>
<
span>
音频文件上传:
</span><br><br
>
<el-upload
<el-upload
class=
"upload-box"
class=
"upload-box"
drag
drag
...
@@ -89,8 +89,18 @@
...
@@ -89,8 +89,18 @@
></el-progress>
></el-progress>
</div>
</div>
<!-- 类型选择 -->
<div
class=
"upload-section"
>
<div
class=
"upload-section"
>
<h3>
PDF文件上传
</h3>
<span>
文件类型选择:
</span>
<el-radio-group
v-model=
"uploadType"
@
change=
"handleUploadTypeChange"
>
<el-radio
label=
"pdf"
>
PDF文件
</el-radio>
<el-radio
label=
"image"
>
图片文件
</el-radio>
</el-radio-group>
</div>
<!-- PDF上传区域 -->
<div
class=
"upload-section"
v-if=
"uploadType === 'pdf'"
>
<span>
PDF文件上传:
</span><br><br>
<el-upload
<el-upload
class=
"upload-box"
class=
"upload-box"
drag
drag
...
@@ -117,6 +127,70 @@
...
@@ -117,6 +127,70 @@
></el-progress>
></el-progress>
</div>
</div>
<!-- 图片上传区域 -->
<div
class=
"upload-section"
v-if=
"uploadType === 'image'"
>
<span>
图片文件上传:
</span><br><br>
<el-upload
class=
"upload-box"
drag
action=
"#"
:auto-upload=
"true"
:http-request=
"handleImageUpload"
:show-file-list=
"false"
:before-upload=
"beforeImageUpload"
accept=
".jpg,.jpeg,.png,.gif,.bmp,.webp"
multiple
>
<i
class=
"el-icon-upload"
></i>
<div
class=
"upload-text"
>
<p>
点击或拖拽文件到此处上传
</p>
<p
class=
"upload-hint"
>
支持多张图片,格式:jpg, jpeg, png, gif, bmp, webp
</p>
</div>
</el-upload>
<!-- 图片列表 -->
<div
class=
"image-list"
v-if=
"imageFiles.length > 0"
>
<h4>
已上传图片列表(可拖拽排序)
</h4>
<div
class=
"image-grid"
>
<div
v-for=
"(file, index) in imageFiles"
:key=
"file.id"
class=
"image-item"
:data-index=
"index"
draggable=
"true"
@
dragstart=
"handleImageDragStart($event, index)"
@
dragover=
"handleImageDragOver($event)"
@
drop=
"handleImageDrop($event, index)"
@
dragend=
"handleImageDragEnd($event)"
>
<div
class=
"image-preview"
>
<img
:src=
"file.url"
:alt=
"file.name"
>
<div
class=
"image-overlay"
>
<el-button
type=
"danger"
size=
"mini"
circle
@
click=
"removeImageFile(file.id)"
class=
"remove-btn"
>
<i
class=
"el-icon-delete"
></i>
</el-button>
</div>
</div>
<div
class=
"image-info"
>
<div
class=
"image-name"
>
{{ file.name }}
</div>
</div>
<el-progress
v-if=
"imageUploadProgress[file.id] && imageUploadProgress[file.id] < 100"
:percentage=
"imageUploadProgress[file.id]"
:format=
"progressFormat"
size=
"mini"
></el-progress>
</div>
</div>
</div>
</div>
<div
class=
"upload-actions"
>
<div
class=
"upload-actions"
>
<el-button
<el-button
type=
"primary"
type=
"primary"
...
...
form/index.js
View file @
2c70614c
...
@@ -3,20 +3,22 @@ var t_name = 'AI_BOOK_TOOL';
...
@@ -3,20 +3,22 @@ var t_name = 'AI_BOOK_TOOL';
const
callServerMethod
=
(
method
,
data
)
=>
{
const
callServerMethod
=
(
method
,
data
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
window
.
courseware
.
onEvent
(
method
,
(
result
)
=>
{
window
.
courseware
.
onEvent
(
method
,
(
result
)
=>
{
console
.
log
(
'
result
'
,
result
);
window
.
courseware
.
removeEvent
(
method
,
t_name
);
resolve
(
result
);
resolve
(
result
);
},
t_name
);
},
t_name
);
window
.
courseware
.
sendEvent
(
method
,
data
,
t_name
);
window
.
courseware
.
sendEvent
(
method
,
data
,
t_name
);
});
});
}
}
window
.
courseware
.
getData
=
function
(
fun
,
key
)
{
//
window.courseware.getData = function (fun, key) {
$
.
getJSON
(
'
../play/defaultData_1.json
'
,
function
(
defaultData
)
{
//
$.getJSON('../play/defaultData_1.json', function(defaultData) {
console
.
log
(
'
加载默认数据:
'
,
defaultData
);
//
console.log('加载默认数据:', defaultData);
fun
(
defaultData
);
//
fun(defaultData);
}).
fail
(
function
()
{
//
}).fail(function() {
console
.
error
(
'
默认数据加载失败!
'
);
//
console.error('默认数据加载失败!');
});
//
});
}
//
}
new
Vue
({
new
Vue
({
...
@@ -91,13 +93,19 @@ new Vue({
...
@@ -91,13 +93,19 @@ new Vue({
hasData
:
false
,
hasData
:
false
,
audioFile
:
null
,
audioFile
:
null
,
pdfFile
:
null
,
pdfFile
:
null
,
imageFiles
:
[],
// 新增:图片文件数组
audioUploadProgress
:
0
,
audioUploadProgress
:
0
,
pdfUploadProgress
:
0
,
pdfUploadProgress
:
0
,
imageUploadProgress
:
{},
// 新增:图片上传进度对象
audioUploadComplete
:
false
,
audioUploadComplete
:
false
,
pdfUploadComplete
:
false
,
pdfUploadComplete
:
false
,
imageUploadComplete
:
false
,
// 新增:图片上传完成状态
isAudioDragover
:
false
,
isAudioDragover
:
false
,
isPdfDragover
:
false
,
isPdfDragover
:
false
,
isImageDragover
:
false
,
// 新增:图片拖拽状态
queueTasks
:
[],
queueTasks
:
[],
// 新增:上传类型
uploadType
:
'
pdf
'
,
},
},
mounted
()
{
mounted
()
{
...
@@ -197,6 +205,7 @@ new Vue({
...
@@ -197,6 +205,7 @@ new Vue({
// 开始预加载图片
// 开始预加载图片
this
.
preloadImages
();
this
.
preloadImages
();
window
.
air
.
hideAirClassLoading
(
t_name
,
data
);
},
},
// 预加载所有图片
// 预加载所有图片
...
@@ -2137,6 +2146,10 @@ new Vue({
...
@@ -2137,6 +2146,10 @@ new Vue({
this
.
$message
.
warning
(
'
只能上传一个文件
'
);
this
.
$message
.
warning
(
'
只能上传一个文件
'
);
},
},
handleImageExceed
()
{
this
.
$message
.
warning
(
'
图片数量超出限制
'
);
},
beforeAudioUpload
(
file
)
{
beforeAudioUpload
(
file
)
{
const
isMP3
=
file
.
type
===
'
audio/mp3
'
||
file
.
type
===
'
audio/mpeg
'
||
file
.
name
.
toLowerCase
().
endsWith
(
'
.mp3
'
);
const
isMP3
=
file
.
type
===
'
audio/mp3
'
||
file
.
type
===
'
audio/mpeg
'
||
file
.
name
.
toLowerCase
().
endsWith
(
'
.mp3
'
);
if
(
!
isMP3
)
{
if
(
!
isMP3
)
{
...
@@ -2153,6 +2166,15 @@ new Vue({
...
@@ -2153,6 +2166,15 @@ new Vue({
return
isPDF
;
return
isPDF
;
},
},
beforeImageUpload
(
file
)
{
const
isImage
=
file
.
type
.
startsWith
(
'
image/
'
)
||
file
.
name
.
toLowerCase
().
match
(
/
\.(
jpg|jpeg|png|gif|bmp|webp
)
$/
);
if
(
!
isImage
)
{
this
.
$message
.
error
(
'
请上传图片格式的文件
'
);
}
return
isImage
;
},
async
handleAudioUpload
(
options
)
{
async
handleAudioUpload
(
options
)
{
const
{
file
}
=
options
;
const
{
file
}
=
options
;
this
.
audioUploadProgress
=
0
;
this
.
audioUploadProgress
=
0
;
...
@@ -2213,6 +2235,45 @@ new Vue({
...
@@ -2213,6 +2235,45 @@ new Vue({
}
}
},
},
async
handleImageUpload
(
options
)
{
const
{
file
}
=
options
;
const
fileId
=
Date
.
now
()
+
Math
.
random
().
toString
(
36
).
substr
(
2
,
9
);
this
.
$set
(
this
.
imageUploadProgress
,
fileId
,
0
);
const
uploadUrl
=
window
.
courseware
.
uploadUrl
();
const
uploadData
=
window
.
courseware
.
uploadData
();
try
{
const
formData
=
new
FormData
();
for
(
const
key
in
uploadData
)
{
formData
.
append
(
key
,
uploadData
[
key
]);
}
formData
.
append
(
'
file
'
,
file
);
const
result
=
await
this
.
uploadFile
(
formData
,
uploadUrl
,
(
progress
)
=>
{
this
.
$set
(
this
.
imageUploadProgress
,
fileId
,
progress
);
});
// 添加到图片文件列表
this
.
imageFiles
.
push
({
id
:
fileId
,
name
:
file
.
name
,
url
:
result
.
url
,
size
:
file
.
size
});
// 按文件名自然排序
this
.
sortImageFiles
();
this
.
$message
.
success
(
`图片
${
file
.
name
}
上传成功`
);
}
catch
(
error
)
{
console
.
error
(
'
Error uploading image:
'
,
error
);
this
.
$message
.
error
(
`图片
${
file
.
name
}
上传失败`
);
this
.
$delete
(
this
.
imageUploadProgress
,
fileId
);
}
},
uploadFile
(
formData
,
url
,
progressCallback
)
{
uploadFile
(
formData
,
url
,
progressCallback
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
const
xhr
=
new
XMLHttpRequest
();
const
xhr
=
new
XMLHttpRequest
();
...
@@ -2251,6 +2312,52 @@ new Vue({
...
@@ -2251,6 +2312,52 @@ new Vue({
this
.
pdfUploadComplete
=
false
;
this
.
pdfUploadComplete
=
false
;
},
},
removeImageFile
(
fileId
)
{
const
index
=
this
.
imageFiles
.
findIndex
(
file
=>
file
.
id
===
fileId
);
if
(
index
>
-
1
)
{
this
.
imageFiles
.
splice
(
index
,
1
);
this
.
$delete
(
this
.
imageUploadProgress
,
fileId
);
}
},
// 按文件名自然排序
sortImageFiles
()
{
this
.
imageFiles
.
sort
((
a
,
b
)
=>
{
return
a
.
name
.
localeCompare
(
b
.
name
,
undefined
,
{
numeric
:
true
,
sensitivity
:
'
base
'
});
});
},
// 拖拽排序相关方法
handleImageDragStart
(
e
,
index
)
{
e
.
dataTransfer
.
effectAllowed
=
'
move
'
;
e
.
dataTransfer
.
setData
(
'
text/html
'
,
e
.
target
.
outerHTML
);
e
.
target
.
style
.
opacity
=
'
0.5
'
;
},
handleImageDragOver
(
e
)
{
e
.
preventDefault
();
e
.
dataTransfer
.
dropEffect
=
'
move
'
;
},
handleImageDrop
(
e
,
targetIndex
)
{
e
.
preventDefault
();
const
sourceIndex
=
parseInt
(
e
.
dataTransfer
.
getData
(
'
text/html
'
).
match
(
/data-index="
(\d
+
)
"/
)?.[
1
]
||
0
);
if
(
sourceIndex
!==
targetIndex
)
{
const
item
=
this
.
imageFiles
.
splice
(
sourceIndex
,
1
)[
0
];
this
.
imageFiles
.
splice
(
targetIndex
,
0
,
item
);
}
// 重置所有拖拽样式
document
.
querySelectorAll
(
'
.image-item
'
).
forEach
(
el
=>
{
el
.
style
.
opacity
=
'
1
'
;
});
},
handleImageDragEnd
(
e
)
{
e
.
target
.
style
.
opacity
=
'
1
'
;
},
async
handleProcess
()
{
async
handleProcess
()
{
if
(
!
this
.
isUploadComplete
)
{
if
(
!
this
.
isUploadComplete
)
{
this
.
$message
.
warning
(
'
请等待文件上传完成
'
);
this
.
$message
.
warning
(
'
请等待文件上传完成
'
);
...
@@ -2258,7 +2365,18 @@ new Vue({
...
@@ -2258,7 +2365,18 @@ new Vue({
}
}
this
.
loading
=
true
;
this
.
loading
=
true
;
window
.
courseware
.
setData
({
audioUrl
:
this
.
audioFile
,
pdfUrl
:
this
.
pdfFile
},
()
=>
{
// 根据上传的文件类型决定传递的数据
let
uploadData
=
{
audioUrl
:
this
.
audioFile
};
if
(
this
.
pdfFile
)
{
uploadData
.
pdfUrl
=
this
.
pdfFile
;
}
else
if
(
this
.
imageFiles
.
length
>
0
)
{
uploadData
.
imageUrls
=
this
.
imageFiles
.
map
(
file
=>
file
.
url
);
}
console
.
log
(
uploadData
);
window
.
courseware
.
setData
(
uploadData
,
()
=>
{
this
.
loading
=
false
;
this
.
loading
=
false
;
this
.
checkProcess
();
this
.
checkProcess
();
},
t_name
);
},
t_name
);
...
@@ -2314,6 +2432,20 @@ new Vue({
...
@@ -2314,6 +2432,20 @@ new Vue({
}
}
});
});
},
},
// 新增:切换上传类型
handleUploadTypeChange
(
val
)
{
if
(
val
===
'
pdf
'
)
{
// 清空图片
this
.
imageFiles
=
[];
this
.
imageUploadProgress
=
{};
}
else
if
(
val
===
'
image
'
)
{
// 清空PDF
this
.
pdfFile
=
null
;
this
.
pdfUploadProgress
=
0
;
this
.
pdfUploadComplete
=
false
;
}
},
},
},
beforeDestroy
()
{
beforeDestroy
()
{
...
@@ -2339,7 +2471,22 @@ new Vue({
...
@@ -2339,7 +2471,22 @@ new Vue({
},
},
computed
:
{
computed
:
{
isUploadComplete
()
{
isUploadComplete
()
{
return
this
.
audioUploadComplete
&&
this
.
pdfUploadComplete
&&
this
.
audioFile
&&
this
.
pdfFile
;
// 音频必须上传完成
if
(
!
this
.
audioUploadComplete
||
!
this
.
audioFile
)
{
return
false
;
}
// PDF或图片必须上传完成(二选一)
if
(
this
.
pdfFile
&&
this
.
pdfUploadComplete
)
{
return
true
;
}
if
(
this
.
imageFiles
.
length
>
0
)
{
// 检查所有图片是否都上传完成
return
this
.
imageFiles
.
every
(
file
=>
!
this
.
imageUploadProgress
[
file
.
id
]
||
this
.
imageUploadProgress
[
file
.
id
]
===
100
);
}
return
false
;
}
}
}
}
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment