Commit 2c70614c authored by liujiangnan's avatar liujiangnan

feat: 支持图片批量上传的方式

parent 7cde8ab7
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: #f6f7fa;
}
#app {
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
}
.el-container {
......@@ -779,21 +784,29 @@ html, body {
/* 上传界面样式 */
.upload-area {
width: 800px;
background-color: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
width: 95vw;
height: 95vh;
min-width: 320px;
min-height: 480px;
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 {
margin-bottom: 30px;
margin-bottom: 2vw;
}
.upload-section h3 {
margin: 0 0 20px 0;
margin: 0 0 1vw 0;
color: #303133;
font-size: 18px;
font-size: 1.3vw;
}
.upload-box {
......@@ -841,7 +854,7 @@ html, body {
.upload-actions {
text-align: center;
margin-top: 30px;
margin-top: 2vw;
}
/* 时间轴样式 */
......@@ -961,4 +974,195 @@ html, body {
.el-timeline-item__node--info {
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, 1fr));
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, 1fr));
gap: 8px;
}
.image-preview {
height: 60px;
}
.image-name {
font-size: 12px;
}
}
\ No newline at end of file
......@@ -62,7 +62,7 @@
<!-- 上传界面 -->
<div class="upload-area" v-else>
<div class="upload-section">
<h3>音频文件上传</h3>
<span>音频文件上传:</span><br><br>
<el-upload
class="upload-box"
drag
......@@ -89,8 +89,18 @@
></el-progress>
</div>
<!-- 类型选择 -->
<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
class="upload-box"
drag
......@@ -117,6 +127,70 @@
></el-progress>
</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">
<el-button
type="primary"
......
......@@ -3,20 +3,22 @@ var t_name = 'AI_BOOK_TOOL';
const callServerMethod = (method, data) => {
return new Promise((resolve, reject) => {
window.courseware.onEvent(method, (result) => {
console.log('result', result);
window.courseware.removeEvent(method, t_name);
resolve(result);
}, t_name);
window.courseware.sendEvent(method, data, t_name);
});
}
window.courseware.getData = function (fun, key) {
$.getJSON('../play/defaultData_1.json', function(defaultData) {
console.log('加载默认数据:', defaultData);
fun(defaultData);
}).fail(function() {
console.error('默认数据加载失败!');
});
}
// window.courseware.getData = function (fun, key) {
// $.getJSON('../play/defaultData_1.json', function(defaultData) {
// console.log('加载默认数据:', defaultData);
// fun(defaultData);
// }).fail(function() {
// console.error('默认数据加载失败!');
// });
// }
new Vue({
......@@ -91,13 +93,19 @@ new Vue({
hasData: false,
audioFile: null,
pdfFile: null,
imageFiles: [], // 新增:图片文件数组
audioUploadProgress: 0,
pdfUploadProgress: 0,
imageUploadProgress: {}, // 新增:图片上传进度对象
audioUploadComplete: false,
pdfUploadComplete: false,
imageUploadComplete: false, // 新增:图片上传完成状态
isAudioDragover: false,
isPdfDragover: false,
isImageDragover: false, // 新增:图片拖拽状态
queueTasks: [],
// 新增:上传类型
uploadType: 'pdf',
},
mounted() {
......@@ -197,6 +205,7 @@ new Vue({
// 开始预加载图片
this.preloadImages();
window.air.hideAirClassLoading(t_name, data);
},
// 预加载所有图片
......@@ -2137,6 +2146,10 @@ new Vue({
this.$message.warning('只能上传一个文件');
},
handleImageExceed() {
this.$message.warning('图片数量超出限制');
},
beforeAudioUpload(file) {
const isMP3 = file.type === 'audio/mp3' || file.type === 'audio/mpeg' || file.name.toLowerCase().endsWith('.mp3');
if (!isMP3) {
......@@ -2153,6 +2166,15 @@ new Vue({
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) {
const { file } = options;
this.audioUploadProgress = 0;
......@@ -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) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
......@@ -2251,6 +2312,52 @@ new Vue({
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() {
if (!this.isUploadComplete) {
this.$message.warning('请等待文件上传完成');
......@@ -2258,7 +2365,18 @@ new Vue({
}
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.checkProcess();
}, t_name);
......@@ -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() {
......@@ -2339,7 +2471,22 @@ new Vue({
},
computed: {
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;
}
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment