Commit 0a1309af authored by 15011343103's avatar 15011343103

feat: 异步调用显示灯

parent 4bc039f6
......@@ -730,3 +730,197 @@ html, body {
pointer-events: none;
}
.custom-audio-marker-blue { color: #00ff00 !important; }
.upload-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.upload-area {
width: 80%;
max-width: 800px;
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.upload-section {
margin-bottom: 30px;
}
.upload-section h3 {
margin-bottom: 15px;
color: #303133;
font-size: 18px;
}
.upload-box {
border: 2px dashed #dcdfe6;
border-radius: 6px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.upload-box:hover {
border-color: #409eff;
}
.upload-box.is-dragover {
border-color: #409eff;
background-color: rgba(64, 158, 255, 0.1);
}
.upload-box i {
font-size: 48px;
color: #909399;
margin-bottom: 15px;
}
.upload-text {
color: #606266;
}
.upload-text p {
margin: 5px 0;
}
.upload-hint {
font-size: 12px;
color: #909399;
}
.file-info {
margin-top: 15px;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-info p {
margin: 0;
color: #606266;
}
.upload-actions {
text-align: center;
margin-top: 30px;
}
/* 时间轴样式 */
.timeline-content {
padding: 10px 0;
}
.timeline-content h4 {
margin: 0 0 10px 0;
color: #303133;
font-size: 16px;
}
.timeline-content .el-progress {
margin: 10px 0;
}
.error-message {
color: #f56c6c;
font-size: 12px;
margin: 5px 0 0 0;
}
.el-timeline-item__node {
background-color: #409eff;
}
.el-timeline-item__node--success {
background-color: #67c23a;
}
.el-timeline-item__node--warning {
background-color: #e6a23c;
}
.el-timeline-item__node--danger {
background-color: #f56c6c;
}
.el-timeline-item__node--info {
background-color: #909399;
}
.el-timeline-item__timestamp {
color: #909399;
font-size: 12px;
}
.el-timeline-item__content {
color: #303133;
}
.el-timeline-item__wrapper {
padding-left: 28px;
position: relative;
padding-bottom: 20px;
}
.el-timeline-item__tail {
position: absolute;
left: 4px;
height: 100%;
border-left: 2px solid #e4e7ed;
}
.el-timeline-item__icon {
color: #fff;
font-size: 13px;
}
.el-timeline-item__node--normal {
left: -1px;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #909399;
}
.el-timeline-item__node--large {
left: -2px;
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #909399;
}
.el-timeline-item__node--primary {
background-color: #409eff;
}
.el-timeline-item__node--success {
background-color: #67c23a;
}
.el-timeline-item__node--warning {
background-color: #e6a23c;
}
.el-timeline-item__node--danger {
background-color: #f56c6c;
}
.el-timeline-item__node--info {
background-color: #909399;
}
\ No newline at end of file
......@@ -29,6 +29,102 @@
</head>
<body>
<div id="app" v-loading.fullscreen.lock="loading">
<!-- 文件上传界面 -->
<div class="upload-container" v-if="!hasData">
<div class="upload-area">
<div class="upload-section">
<h3>音频文件上传</h3>
<el-upload
class="upload-box"
drag
action="#"
:auto-upload="true"
:http-request="handleAudioUpload"
:show-file-list="true"
:limit="1"
:on-exceed="handleExceed"
:before-upload="beforeAudioUpload"
:on-remove="removeAudioFile"
accept=".mp3"
>
<i class="el-icon-upload"></i>
<div class="upload-text">
<p>点击或拖拽文件到此处上传</p>
<p class="upload-hint">支持 .mp3 格式</p>
</div>
</el-upload>
<el-progress
v-if="audioUploadProgress > 0 && audioUploadProgress < 100"
:percentage="audioUploadProgress"
:format="progressFormat"
></el-progress>
</div>
<div class="upload-section">
<h3>PDF文件上传</h3>
<el-upload
class="upload-box"
drag
action="#"
:auto-upload="true"
:http-request="handlePdfUpload"
:show-file-list="true"
:limit="1"
:on-exceed="handleExceed"
:before-upload="beforePdfUpload"
:on-remove="removePdfFile"
accept=".pdf"
>
<i class="el-icon-upload"></i>
<div class="upload-text">
<p>点击或拖拽文件到此处上传</p>
<p class="upload-hint">支持 .pdf 格式</p>
</div>
</el-upload>
<el-progress
v-if="pdfUploadProgress > 0 && pdfUploadProgress < 100"
:percentage="pdfUploadProgress"
:format="progressFormat"
></el-progress>
</div>
<!-- 添加任务处理进度时间轴 -->
<div class="upload-section" v-if="queueTasks.length > 0">
<h3>处理进度</h3>
<el-timeline>
<el-timeline-item
v-for="(task, index) in queueTasks"
:key="index"
:type="getTimelineItemType(task.step_status)"
:color="getTimelineItemColor(task.step_status)"
:timestamp="task.create_at"
>
<div class="timeline-content">
<h4>{{ task.step_code_desc }}</h4>
<el-progress
:percentage="task.step_process"
:status="getProgressStatus(task.step_status)"
></el-progress>
<p v-if="task.err_massage" class="error-message">{{ task.err_massage }}</p>
</div>
</el-timeline-item>
</el-timeline>
</div>
<div class="upload-actions">
<el-button
type="primary"
size="large"
:disabled="!isUploadComplete"
@click="handleProcess"
>
开始处理
</el-button>
</div>
</div>
</div>
<el-container v-else>
<el-container>
<!-- 主要内容区域 -->
<el-main>
......@@ -348,6 +444,7 @@
</div>
</div>
</div>
</el-container>
</div>
</body>
<script src="./index.js"></script>
......
......@@ -67,6 +67,17 @@ new Vue({
lastCheckedTime: 0, // 用于优化检查间隔
isDataModified: false,
hasData: false,
audioFile: null,
pdfFile: null,
audioUploadProgress: 0,
pdfUploadProgress: 0,
audioUploadComplete: false,
pdfUploadComplete: false,
isAudioDragover: false,
isPdfDragover: false,
queueTasks: [],
},
mounted() {
......@@ -86,6 +97,28 @@ new Vue({
},
methods: {
// 心跳实时查询异步处理进度及信息
async checkProcess(_data) {
let data = _data;
if (!data) {
data = await new Promise((resolve) => {
window.courseware.getData((data) => {
resolve(data);
}, t_name);
});
}
if (data && data.data_type == 'AI_QUEUE') {
// 更新队列任务列表
this.queueTasks = data.rows;
// 继续检查进度
await new Promise(resolve => setTimeout(resolve, 1000));
this.checkProcess();
} else {
this.hasData = true;
this.initPage(data);
this.$message.success('处理完成');
}
},
// 加载默认数据
async loadDefaultData() {
this.loading = true;
......@@ -99,10 +132,28 @@ new Vue({
});
if (!data) {
this.loading = false;
// TODO 上传文件以及上传后的AI处理状态展示,直到AI处理完成才会有 data
return;
}
if (data && data.data_type == 'AI_QUEUE') {
// 说明是AI处理队列,需要实时查询进度
this.loading = false;
this.checkProcess();
return;
}
this.hasData = true;
this.initPage(data);
this.loading = false;
this.$message.success('数据加载成功');
} catch (error) {
console.error('Error loading data:', error);
this.$message.error('数据加载失败');
this.loading = false;
throw error;
}
},
initPage(data) {
this.audioPoints = data.audio_points;
this.pages = data.pages;
......@@ -113,15 +164,6 @@ new Vue({
// 开始预加载图片
this.preloadImages();
this.loading = false;
this.$message.success('数据加载成功');
} catch (error) {
console.error('Error loading data:', error);
this.$message.error('数据加载失败');
this.loading = false;
throw error;
}
},
// 预加载所有图片
......@@ -1072,7 +1114,7 @@ new Vue({
console.error('Error deleting rectangle from group:', err);
this.$message.error('删除失败');
}
}).catch(() => {});
}).catch(() => { });
},
// 删除整个分组
......@@ -1108,7 +1150,7 @@ new Vue({
console.error('Error deleting group:', err);
this.$message.error('删除失败');
}
}).catch(() => {});
}).catch(() => { });
},
// 处理矩形列表行点击
......@@ -2039,12 +2081,12 @@ new Vue({
window.courseware.setData(data, () => {
console.log('----save----');
this.isDataModified = false;
},t_name);
}, t_name);
},
// 处理秒的显示
formatTime(_seconds) {
let seconds = Math.round(_seconds*100)/100;
let seconds = Math.round(_seconds * 100) / 100;
// 小数点后保留两位,如果只有一位则补充 0
if (seconds.toString().indexOf('.') === -1) {
seconds = seconds + '.00';
......@@ -2052,7 +2094,172 @@ new Vue({
seconds = seconds + '0';
}
return seconds.toString();
},
progressFormat(percentage) {
return percentage === 100 ? '上传完成' : `${percentage}%`;
},
handleExceed() {
this.$message.warning('只能上传一个文件');
},
beforeAudioUpload(file) {
const isMP3 = file.type === 'audio/mp3' || file.type === 'audio/mpeg' || file.name.toLowerCase().endsWith('.mp3');
if (!isMP3) {
this.$message.error('请上传 MP3 格式的音频文件');
}
return isMP3;
},
beforePdfUpload(file) {
const isPDF = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf');
if (!isPDF) {
this.$message.error('请上传 PDF 格式的文件');
}
return isPDF;
},
async handleAudioUpload(options) {
const { file } = options;
this.audioUploadProgress = 0;
this.audioUploadComplete = false;
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.audioUploadProgress = progress;
});
this.audioFile = result.url;
this.audioUploadComplete = true;
this.$message.success('音频文件上传成功');
} catch (error) {
console.error('Error uploading audio:', error);
this.$message.error('音频文件上传失败');
this.audioUploadProgress = 0;
this.audioUploadComplete = false;
}
},
async handlePdfUpload(options) {
const { file } = options;
this.pdfUploadProgress = 0;
this.pdfUploadComplete = false;
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.pdfUploadProgress = progress;
});
this.pdfFile = result.url;
this.pdfUploadComplete = true;
this.$message.success('PDF文件上传成功');
} catch (error) {
console.error('Error uploading PDF:', error);
this.$message.error('PDF文件上传失败');
this.pdfUploadProgress = 0;
this.pdfUploadComplete = false;
}
},
uploadFile(formData, url, progressCallback) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
progressCallback(percent);
}
};
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error('Upload failed'));
}
}
};
xhr.send(formData);
});
},
removeAudioFile() {
this.audioFile = null;
this.audioUploadProgress = 0;
this.audioUploadComplete = false;
},
removePdfFile() {
this.pdfFile = null;
this.pdfUploadProgress = 0;
this.pdfUploadComplete = false;
},
async handleProcess() {
if (!this.isUploadComplete) {
this.$message.warning('请等待文件上传完成');
return;
}
this.loading = true;
window.courseware.setData({ audioUrl: this.audioFile, pdfUrl: this.pdfFile }, () => {
this.loading = false;
this.checkProcess();
}, t_name);
},
getTimelineItemType(status) {
switch (status) {
case '1': return 'info'; // 未开始
case '2': return 'primary'; // 进行中
case '3': return 'success'; // 已完成
case '4': return 'danger'; // 失败
default: return 'info';
}
},
getTimelineItemColor(status) {
switch (status) {
case '1': return '#909399'; // 未开始
case '2': return '#409eff'; // 进行中
case '3': return '#67c23a'; // 已完成
case '4': return '#f56c6c'; // 失败
default: return '#909399';
}
},
getProgressStatus(status) {
switch (status) {
case '1': return ''; // 未开始
case '2': return ''; // 进行中
case '3': return 'success'; // 已完成
case '4': return 'exception'; // 失败
default: return '';
}
},
},
beforeDestroy() {
......@@ -2075,5 +2282,10 @@ new Vue({
console.error('Error destroying wavesurfer:', err);
}
}
},
computed: {
isUploadComplete() {
return this.audioUploadComplete && this.pdfUploadComplete && this.audioFile && this.pdfFile;
}
}
});
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