feat: 添加任务状态级联触发器,优化支付和做同款功能
主要更新: - 添加 MySQL 触发器实现 task_status 表到其他表的状态级联 - 移除控制器中的多表状态检查代码 - 完善做同款功能,支持参数传递 - 支付宝 USD 转 CNY 汇率转换 - 修复状态枚举映射问题 注意: 触发器仅在 task_status 更新时触发,部分代码仍直接更新业务表
This commit is contained in:
@@ -176,9 +176,19 @@
|
||||
@error="onImageError"
|
||||
/>
|
||||
<!-- 否则使用默认占位符 -->
|
||||
<div v-else class="work-placeholder">
|
||||
<div v-else class="work-placeholder" :class="{ 'is-processing': item.status === 'PROCESSING' || item.status === 'PENDING' }">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
<div class="placeholder-text">{{ item.status === 'PROCESSING' ? t('works.processing') : t('works.noPreview') }}</div>
|
||||
<div class="placeholder-text">{{ item.status === 'PROCESSING' ? t('works.processing') : (item.status === 'PENDING' ? t('works.queuing') : t('works.noPreview')) }}</div>
|
||||
</div>
|
||||
<!-- 生成中/排队中状态的覆盖层(始终显示) -->
|
||||
<div v-if="item.status === 'PROCESSING' || item.status === 'PENDING'" class="processing-overlay">
|
||||
<div class="processing-content">
|
||||
<el-icon class="processing-icon"><VideoPlay /></el-icon>
|
||||
<div class="processing-text">{{ item.status === 'PROCESSING' ? t('works.processing') : t('works.queuing') }}</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-animated"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checker" v-if="multiSelect">
|
||||
@@ -396,6 +406,11 @@
|
||||
<span>{{ t('profile.systemSettings') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 修改密码 -->
|
||||
<div class="menu-item" @click.stop="goToChangePassword" style="cursor: pointer;">
|
||||
<el-icon><Lock /></el-icon>
|
||||
<span>{{ t('profile.changePassword') }}</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="logout">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>{{ t('common.logout') }}</span>
|
||||
@@ -409,7 +424,7 @@
|
||||
import { ref, onMounted, onActivated, computed, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Star, User, Compass, Document, VideoPlay, Picture, Film, Search, MoreFilled, Loading, ArrowUp, VideoCamera, Refresh, Delete, CopyDocument, Download, Close } from '@element-plus/icons-vue'
|
||||
import { Star, User, Compass, Document, VideoPlay, Picture, Film, Search, MoreFilled, Loading, ArrowUp, VideoCamera, Refresh, Delete, CopyDocument, Download, Close, Setting, Lock } from '@element-plus/icons-vue'
|
||||
import { getMyWorks, getWorkDetail, deleteWork, recordDownload, getWorkFileUrl } from '@/api/userWorks'
|
||||
import { getCurrentUser } from '@/api/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -578,6 +593,11 @@ const goToSettings = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const goToChangePassword = () => {
|
||||
showUserMenu.value = false
|
||||
router.push('/change-password')
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logout = async () => {
|
||||
try {
|
||||
@@ -618,7 +638,9 @@ const loadList = async () => {
|
||||
})
|
||||
|
||||
// 转换数据格式
|
||||
const transformedData = data.map(transformWorkData)
|
||||
const transformedData = data
|
||||
.map(transformWorkData)
|
||||
.filter(work => work.status !== 'FAILED' && work.status !== 'DELETED')
|
||||
|
||||
// 调试日志: 查看转换后的数据
|
||||
console.log('转换后的作品数据:', transformedData)
|
||||
@@ -924,18 +946,19 @@ const deleteFailedWork = async () => {
|
||||
)
|
||||
|
||||
// 执行删除
|
||||
console.log('删除作品:', selectedItem.value.id)
|
||||
const response = await deleteWork(selectedItem.value.id)
|
||||
const itemId = selectedItem.value.id // 先保存 id,因为 handleClose 会将 selectedItem 设为 null
|
||||
console.log('删除作品:', itemId)
|
||||
const response = await deleteWork(itemId)
|
||||
|
||||
if (response.data.success) {
|
||||
ElMessage.success(t('works.deleteSuccess'))
|
||||
|
||||
// 从列表中移除该作品(在关闭详情页之前)
|
||||
items.value = items.value.filter(item => item.id !== itemId)
|
||||
|
||||
// 关闭详情页
|
||||
handleClose()
|
||||
|
||||
// 从列表中移除该作品
|
||||
items.value = items.value.filter(item => item.id !== selectedItem.value.id)
|
||||
|
||||
// 或者重新加载列表
|
||||
// reload()
|
||||
} else {
|
||||
@@ -951,19 +974,31 @@ const deleteFailedWork = async () => {
|
||||
|
||||
// 创建同款
|
||||
const createSimilar = (item) => {
|
||||
if (item) {
|
||||
ElMessage.info(t('works.createSimilarInfo', { title: item.title }))
|
||||
// 根据作品类型跳转到相应的创建页面
|
||||
if (item.type === 'video') {
|
||||
router.push('/text-to-video/create')
|
||||
} else if (item.type === 'image') {
|
||||
router.push('/image-to-video/create')
|
||||
} else {
|
||||
router.push('/text-to-video/create')
|
||||
}
|
||||
} else {
|
||||
if (!item) {
|
||||
ElMessage.info(t('works.goToCreate'))
|
||||
return
|
||||
}
|
||||
|
||||
// 根据作品类别跳转到对应的创建页面,并携带参数
|
||||
const query = {
|
||||
taskId: item.taskId,
|
||||
prompt: item.prompt || '',
|
||||
aspectRatio: item.aspectRatio || '',
|
||||
duration: item.duration || ''
|
||||
}
|
||||
|
||||
if (item.category === '文生视频') {
|
||||
router.push({ path: '/text-to-video/create', query })
|
||||
} else if (item.category === '图生视频') {
|
||||
router.push({ path: '/image-to-video/create', query })
|
||||
} else if (item.category === '分镜视频') {
|
||||
router.push({ path: '/storyboard-video/create', query })
|
||||
} else {
|
||||
// 默认跳转到文生视频
|
||||
router.push({ path: '/text-to-video/create', query })
|
||||
}
|
||||
|
||||
ElMessage.success(t('works.createSimilarInfo', { title: item.title }))
|
||||
}
|
||||
|
||||
const download = async (item) => {
|
||||
@@ -974,7 +1009,7 @@ const download = async (item) => {
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.success(t('works.downloadStart', { title: item.title }))
|
||||
ElMessage.info(t('works.downloadStart', { title: item.title }))
|
||||
|
||||
// 记录下载次数
|
||||
try {
|
||||
@@ -983,7 +1018,41 @@ const download = async (item) => {
|
||||
console.warn('记录下载次数失败:', err)
|
||||
}
|
||||
|
||||
// 构建下载URL,使用代理下载模式(download=true)避免 CORS 问题
|
||||
// 尝试直接从 resultUrl 下载(绕过后端代理)
|
||||
const videoUrl = item.resultUrl
|
||||
console.log('直接下载视频URL:', videoUrl)
|
||||
|
||||
try {
|
||||
const response = await fetch(videoUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status}`)
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
console.log('文件大小:', blob.size, 'bytes')
|
||||
|
||||
if (blob.size === 0) {
|
||||
throw new Error('文件内容为空')
|
||||
}
|
||||
|
||||
const blobUrl = window.URL.createObjectURL(blob)
|
||||
const filename = `${item.title || 'work'}_${Date.now()}${item.type === 'video' ? '.mp4' : '.png'}`
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = blobUrl
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
|
||||
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000)
|
||||
ElMessage.success(t('works.downloadComplete'))
|
||||
return
|
||||
} catch (directError) {
|
||||
console.warn('直接下载失败,尝试后端代理:', directError)
|
||||
}
|
||||
|
||||
// 备用方案:使用后端代理
|
||||
const downloadUrl = getWorkFileUrl(item.id, true)
|
||||
const token = sessionStorage.getItem('token')
|
||||
|
||||
@@ -1773,6 +1842,22 @@ onActivated(() => {
|
||||
.filters-bar :deep(.el-select__placeholder) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 搜索框深色样式 */
|
||||
.filters-bar :deep(.el-input__wrapper) {
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.filters-bar :deep(.el-input__inner) {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.filters-bar :deep(.el-input__inner::placeholder) {
|
||||
color: rgba(255, 255, 255, 0.6) !important;
|
||||
}
|
||||
|
||||
.select-row { padding: 4px 0 8px; }
|
||||
.works-grid {
|
||||
margin-top: 12px;
|
||||
@@ -1820,6 +1905,84 @@ onActivated(() => {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 动态进度条 */
|
||||
.progress-bar-container {
|
||||
width: 60%;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.progress-bar-animated {
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #409eff, #67c23a, #409eff);
|
||||
background-size: 200% 100%;
|
||||
border-radius: 2px;
|
||||
animation: progress-move 1.5s ease-in-out infinite, progress-gradient 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-move {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(233%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progress-gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 生成中/排队中状态覆盖层 */
|
||||
.processing-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.processing-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.processing-icon {
|
||||
font-size: 32px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.processing-text {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.work-placeholder.is-processing {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.checker { position: absolute; left: 6px; top: 6px; }
|
||||
.actions { position: absolute; right: 6px; top: 6px; display: flex; gap: 4px; opacity: 0; transition: opacity .2s ease; }
|
||||
.thumb:hover .actions { opacity: 1; }
|
||||
@@ -2273,4 +2436,58 @@ onActivated(() => {
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 全局下拉框深色样式(弹出层传送到body需要非scoped样式) -->
|
||||
<style>
|
||||
.el-select-dropdown {
|
||||
background-color: #1a1a1a !important;
|
||||
border: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item {
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item:hover,
|
||||
.el-select-dropdown__item.hover {
|
||||
background-color: #2a2a2a !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.is-selected {
|
||||
color: #409eff !important;
|
||||
background-color: rgba(64, 158, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.el-popper.is-light {
|
||||
background-color: #1a1a1a !important;
|
||||
border: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
.el-popper.is-light .el-popper__arrow::before {
|
||||
background-color: #1a1a1a !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 非 scoped 样式用于 @keyframes 动画 -->
|
||||
<style>
|
||||
@keyframes progress-move {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(233%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progress-gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user