头像视频合成,上传minio,查看

This commit is contained in:
ZLI263
2025-10-08 11:51:55 +08:00
parent 6209b691ab
commit 1a4a921677
3 changed files with 367 additions and 2 deletions

View File

@@ -213,7 +213,7 @@ export const constantRoutes = [
path: 'audio-generation',
name: 'AudioGeneration',
component: () => import('@/views/audio-statistics/audio-generation'),
meta: { title: '音频生成', icon: 'el-icon-microphone' }
meta: { title: '音频列表', icon: 'el-icon-microphone' }
}
]
},

View File

@@ -66,6 +66,10 @@
<div slot="header">
<span>TTS请求日志列表</span>
<div style="float: right;">
<el-button type="info" size="small" @click="handleOpenUploadDialog">
<i class="el-icon-upload" />
上传音频
</el-button>
<el-button type="success" size="small" @click="handleOpenGenerateDialog">
<i class="el-icon-plus" />
音频生成
@@ -207,6 +211,68 @@
</div>
</el-dialog>
<!-- 上传音频对话框 -->
<el-dialog
title="上传音频"
:visible.sync="uploadDialogVisible"
width="600px"
:close-on-click-modal="false"
@close="handleUploadDialogClose"
>
<el-form
ref="uploadForm"
:model="uploadForm"
:rules="uploadFormRules"
label-width="100px"
>
<el-form-item label="选择音频" prop="audioFile">
<el-upload
ref="audioUpload"
:file-list="uploadFileList"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:auto-upload="false"
:limit="1"
accept="audio/*"
drag
>
<i class="el-icon-upload" />
<div class="el-upload__text">将音频文件拖到此处<em>点击上传</em></div>
<div slot="tip" class="el-upload__tip">只能上传音频文件且不超过50MB</div>
</el-upload>
</el-form-item>
<el-form-item label="音频名称" prop="audioName">
<el-input
v-model="uploadForm.audioName"
placeholder="请输入音频名称"
clearable
/>
</el-form-item>
<el-form-item label="创建人姓名" prop="creatorName">
<el-input
v-model="uploadForm.creatorName"
placeholder="请输入创建人姓名"
clearable
/>
</el-form-item>
<el-form-item label="创建人电话" prop="creatorPhone">
<el-input
v-model="uploadForm.creatorPhone"
placeholder="请输入创建人电话"
clearable
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="uploadDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="uploadLoading" @click="handleUploadSubmit">确认提交</el-button>
</div>
</el-dialog>
<!-- 音频生成对话框 -->
<el-dialog
title="音频生成"
@@ -408,6 +474,27 @@ export default {
sortOrder: '',
detailDialogVisible: false,
currentRow: {},
// 上传音频对话框相关
uploadDialogVisible: false,
uploadLoading: false,
uploadFileList: [],
uploadForm: {
audioName: '',
creatorName: '',
creatorPhone: ''
},
uploadFormRules: {
audioName: [
{ required: true, message: '请输入音频名称', trigger: 'blur' }
],
creatorName: [
{ required: true, message: '请输入创建人姓名', trigger: 'blur' }
],
creatorPhone: [
{ required: true, message: '请输入创建人电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
},
// 音频生成对话框相关
generateDialogVisible: false,
generateLoading: false,
@@ -607,6 +694,173 @@ export default {
return statusMap[status] || status
},
// 打开上传音频对话框
handleOpenUploadDialog() {
this.uploadDialogVisible = true
this.uploadForm = {
audioName: '',
creatorName: this.name || '',
creatorPhone: this.phone || ''
}
this.uploadFileList = []
},
// 上传音频对话框关闭
handleUploadDialogClose() {
this.uploadDialogVisible = false
this.uploadFileList = []
this.$refs.uploadForm.resetFields()
},
// 文件变化处理
handleFileChange(file, fileList) {
console.log('文件变化:', file.name)
this.uploadFileList = fileList
// 文件验证
if (file.size > 50 * 1024 * 1024) {
this.$message.error('文件大小不能超过50MB')
this.uploadFileList = []
return
}
const allowedTypes = ['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/m4a', 'audio/aac']
if (!allowedTypes.includes(file.raw?.type || file.type)) {
this.$message.error('只能上传音频文件')
this.uploadFileList = []
return
}
},
// 文件移除处理
handleFileRemove(file, fileList) {
console.log('文件移除:', file.name)
this.uploadFileList = fileList
},
// 上传音频提交
handleUploadSubmit() {
if (this.uploadLoading) {
return
}
this.$refs.uploadForm.validate(valid => {
if (valid) {
if (this.uploadFileList.length === 0) {
this.$message.error('请先选择要上传的音频文件')
return
}
this.uploadLoading = true
this.uploadAudioFile()
} else {
this.$message.error('请完善表单信息')
}
})
},
// 上传音频文件
async uploadAudioFile() {
try {
const file = this.uploadFileList[0].raw || this.uploadFileList[0]
console.log('准备上传音频文件:', file)
// 创建FormData
const formData = new FormData()
formData.append('file', file)
formData.append('audioName', this.uploadForm.audioName)
formData.append('creatorName', this.uploadForm.creatorName)
formData.append('creatorPhone', this.uploadForm.creatorPhone)
console.log('FormData内容:', {
file: file.name,
audioName: this.uploadForm.audioName,
creatorName: this.uploadForm.creatorName,
creatorPhone: this.uploadForm.creatorPhone
})
// 手动上传文件
const uploadResponse = await this.uploadFile(formData)
console.log('上传响应:', uploadResponse)
if (uploadResponse && uploadResponse.success) {
this.$message.success(uploadResponse.message || '音频上传成功')
this.uploadDialogVisible = false
this.fetchData()
// 打印上传成功的详细信息
console.log('上传成功详情:', {
id: uploadResponse.id,
audioName: uploadResponse.audioName,
minioUrl: uploadResponse.minioUrl,
shortUrl: uploadResponse.shortUrl,
shortUrlExpireTime: uploadResponse.shortUrlExpireTime,
creatorName: uploadResponse.creatorName,
creatorPhone: uploadResponse.creatorPhone,
audioSizeBytes: uploadResponse.audioSizeBytes,
status: uploadResponse.status,
createTime: uploadResponse.createTime
})
} else {
this.$message.error(uploadResponse?.message || '音频上传失败')
}
} catch (error) {
console.error('上传过程出错:', error)
this.$message.error('上传失败: ' + (error.message || '未知错误'))
} finally {
this.uploadLoading = false
}
},
// 手动上传文件的方法
async uploadFile(formData) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = () => {
console.log('上传完成,状态码:', xhr.status)
console.log('上传响应:', xhr.responseText)
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText)
resolve(response)
} catch (e) {
console.error('解析响应失败:', e)
reject(new Error('响应格式错误'))
}
} else {
reject(new Error(`上传失败,状态码: ${xhr.status}`))
}
}
xhr.onerror = () => {
console.error('上传请求失败')
reject(new Error('网络请求失败'))
}
xhr.ontimeout = () => {
console.error('上传超时')
reject(new Error('上传超时'))
}
// 设置超时时间
xhr.timeout = 60000
// 先打开连接
xhr.open('POST', '/api/tts/upload-audio')
// 然后设置请求头
const token = this.$store.getters.token
if (token) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token)
}
// 最后发送请求
xhr.send(formData)
})
},
// 格式化文件大小
formatFileSize(bytes) {
if (!bytes) return '-'

View File

@@ -99,7 +99,11 @@
<el-table-column label="检测数量" prop="faceCount" width="100" align="center" />
<el-table-column label="状态" prop="success" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.success ? 'success' : 'danger'">
<el-tag
:type="scope.row.success ? 'success' : 'danger'"
:class="{ 'clickable-failure': !scope.row.success }"
@click="!scope.row.success ? handleFailureClick(scope.row) : null"
>
{{ scope.row.success ? '成功' : '失败' }}
</el-tag>
</template>
@@ -179,6 +183,25 @@
</div>
</el-dialog>
<!-- 失败原因弹窗 -->
<el-dialog
title="失败原因"
:visible.sync="failureDialogVisible"
width="600px"
@close="handleFailureDialogClose"
>
<div class="failure-content">
<p><strong>请求ID:</strong> {{ currentFailureRecord && currentFailureRecord.requestId }}</p>
<p><strong>失败原因:</strong></p>
<div class="failure-reason">
<pre>{{ failureReason }}</pre>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="failureDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
<!-- 上传并检测对话框 -->
<el-dialog
title="上传并检测"
@@ -255,6 +278,10 @@ export default {
detectLoading: false,
detailVisible: false,
currentRecord: null,
// 失败原因弹窗相关
failureDialogVisible: false,
currentFailureRecord: null,
failureReason: '',
// 上传头像相关 - 简化版
uploadAvatarDialogVisible: false,
uploadFileList: [],
@@ -961,6 +988,50 @@ export default {
} catch (e) {
return data
}
},
// 处理失败状态点击
handleFailureClick(row) {
console.log('点击失败状态,记录:', row)
this.currentFailureRecord = row
// 解析responseData中的output内容
let failureReason = '未知错误'
if (row.responseData) {
try {
const responseData = JSON.parse(row.responseData)
if (responseData.output) {
// 提取output中的内容作为失败原因
if (responseData.output.message) {
failureReason = responseData.output.message
} else if (responseData.output.error) {
failureReason = responseData.output.error
} else if (responseData.output.reason) {
failureReason = responseData.output.reason
} else {
// 如果没有具体的错误信息显示整个output对象
failureReason = JSON.stringify(responseData.output, null, 2)
}
} else {
failureReason = JSON.stringify(responseData, null, 2)
}
} catch (e) {
console.error('解析responseData失败:', e)
failureReason = row.responseData
}
} else if (row.errorMessage) {
failureReason = row.errorMessage
}
this.failureReason = failureReason
this.failureDialogVisible = true
},
// 失败原因弹窗关闭
handleFailureDialogClose() {
this.failureDialogVisible = false
this.currentFailureRecord = null
this.failureReason = ''
}
}
}
@@ -1054,4 +1125,44 @@ export default {
text-align: left;
margin-top: 20px;
}
/* 失败状态可点击样式 */
.clickable-failure {
cursor: pointer;
transition: all 0.3s;
}
.clickable-failure:hover {
opacity: 0.8;
transform: scale(1.05);
}
/* 失败原因弹窗样式 */
.failure-content {
padding: 10px 0;
}
.failure-content p {
margin-bottom: 15px;
color: #303133;
}
.failure-reason {
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 15px;
max-height: 300px;
overflow-y: auto;
}
.failure-reason pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
color: #606266;
}
</style>