feat: 系统功能更新 - 添加错误统计、数据初始化、订单调度等功能

This commit is contained in:
AIGC Developer
2025-12-20 15:24:58 +08:00
parent 0933031b59
commit 5344148a1c
70 changed files with 3649 additions and 688 deletions

View File

@@ -42,6 +42,10 @@
<el-icon><Setting /></el-icon>
<span>{{ t('profile.systemSettings') }}</span>
</div>
<div class="menu-item" @click.stop="goToErrorStats">
<el-icon><Warning /></el-icon>
<span>错误统计</span>
</div>
</template>
<!-- 修改密码所有登录用户可见 -->
<div class="menu-item" @click.stop="goToChangePassword" style="cursor: pointer;">
@@ -166,8 +170,9 @@
rows="6"
></textarea>
<div class="input-tips">
<div class="tip-item warning">{{ t('video.storyboard.tipWarning') }}</div>
<div class="tip-item">{{ t('video.storyboard.tip1') }}</div>
<div class="tip-item">{{ t('video.storyboard.tip2') }}</div>
<div class="tip-item points">💎 {{ t('video.storyboard.imageCost') }}</div>
</div>
<div class="optimize-btn">
<button type="button" class="optimize-button" @click="handleStep1ButtonClick" :disabled="!inputText.trim() || inProgress">
@@ -249,6 +254,7 @@
<div class="input-tips">
<div class="tip-item">{{ t('video.storyboard.videoTip1') }}</div>
<div class="tip-item">{{ t('video.storyboard.videoTip2') }}</div>
<div class="tip-item points">💎 {{ t('video.storyboard.videoCost') }}</div>
</div>
<div class="optimize-btn">
<button type="button" class="optimize-button" @click="handleStep2ButtonClick" :disabled="!mainReferenceImage || inProgress">
@@ -276,6 +282,7 @@
</select>
</div>
<!-- 高清模式暂时屏蔽
<div class="setting-item">
<label>{{ t('video.storyboard.hdMode') }}</label>
<div class="hd-setting">
@@ -283,6 +290,7 @@
<span class="cost-text">{{ t('video.storyboard.hdCost') }}</span>
</div>
</div>
-->
</div>
</div>
@@ -297,6 +305,7 @@
:disabled="isGenerateButtonDisabled"
>
{{ isAuthenticated ? getButtonText() : t('video.storyboard.pleaseLogin') }}
<span v-if="isAuthenticated" class="btn-points">30 <el-icon><Star /></el-icon></span>
</button>
<div v-if="!isAuthenticated" class="login-tip-floating">
<p>{{ t('video.storyboard.loginRequired') }}</p>
@@ -315,9 +324,12 @@
</div>
<!-- 任务描述 -->
<div class="task-description" v-if="inputText">
<div class="task-description" v-if="currentStep === 'generate' && inputText">
{{ inputText }}
</div>
<div class="task-description" v-else-if="currentStep === 'video' && videoPrompt">
{{ videoPrompt }}
</div>
<!-- 视频预览区域 -->
<div class="video-preview-container">
@@ -534,7 +546,7 @@
import { ref, computed, onBeforeUnmount, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import { User, VideoCamera, Star, Setting, SwitchButton, Lock, Document } from '@element-plus/icons-vue'
import { User, VideoCamera, Star, Setting, SwitchButton, Lock, Document, Warning } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
import { createStoryboardTask, getStoryboardTask, getUserStoryboardTasks, retryStoryboardTask } from '@/api/storyboardVideo'
import { imageToVideoApi } from '@/api/imageToVideo'
@@ -556,9 +568,9 @@ const isAuthenticated = computed(() => userStore.isAuthenticated)
const inputText = ref('')
const videoPrompt = ref('') // 视频生成提示词
const aspectRatio = ref('16:9')
const duration = ref('10')
const duration = ref('15')
const hdMode = ref(false)
const imageModel = ref('nano-banana')
const imageModel = ref('nano-banana2')
const inProgress = ref(false)
const currentStep = ref('generate') // 'generate' 或 'video'
@@ -687,6 +699,11 @@ const goToSystemSettings = () => {
router.push('/system-settings')
}
const goToErrorStats = () => {
showUserMenu.value = false
router.push('/admin/error-statistics')
}
const goToChangePassword = () => {
showUserMenu.value = false
router.push('/change-password')
@@ -801,18 +818,6 @@ const getButtonText = () => {
return t('video.generate')
}
// 第一步(生成分镜图):根据是否有参考图和提示词显示不同文本
if (hasUploadedImages.value && inputText.value.trim()) {
// 有参考图和提示词:显示"生成分镜图"
return t('video.storyboard.generateStoryboard')
}
if (hasUploadedImages.value) {
// 只有参考图没有提示词:提示输入描述
return t('video.storyboard.generateStoryboard')
}
if (inputText.value.trim()) {
// 只有提示词没有参考图
return t('video.storyboard.generateStoryboard')
}
return t('video.storyboard.generateStoryboard')
}
@@ -1422,6 +1427,7 @@ const pollTaskStatus = async (taskId) => {
const maxAttempts = 90 // 最大尝试次数(足够覆盖整个流程)
let attempts = 0
let currentInterval = 20000 // 初始间隔20秒分镜图生成阶段
let promptWaitAttempts = 0
const poll = async () => {
// 先检查是否超过最大尝试次数
@@ -1575,22 +1581,31 @@ const pollTaskStatus = async (taskId) => {
// 如果进度 < 100说明只是分镜图完成视频还没生成
// 注意如果正在生成视频inProgress=true不要停止轮询
if (taskProgress < 100 && !inProgress.value) {
// 检查提示词是否已经获取到(后端异步优化可能稍晚完成)
const hasPrompts = task.imagePrompt || task.videoPrompt
if (!hasPrompts) {
// 提示词还没就绪,继续轮询(缩短间隔)
console.log('[轮询] 分镜图已生成但提示词未就绪,继续轮询')
currentStep.value = 'video'
pollIntervalId.value = setTimeout(poll, 5000) // 5秒后再试
const imagePromptStr = task.imagePrompt ? String(task.imagePrompt).trim() : ''
const videoPromptStr = task.videoPrompt ? String(task.videoPrompt).trim() : ''
const hasImagePrompt = !!imagePromptStr && imagePromptStr !== 'null'
const hasVideoPrompt = !!videoPromptStr && videoPromptStr !== 'null'
currentStep.value = 'video'
// videoPrompt 还没就绪时:先用 imagePrompt 临时填充,继续短间隔轮询等待真正 videoPrompt
if (!hasVideoPrompt) {
if (!videoPrompt.value.trim() && hasImagePrompt) {
videoPrompt.value = imagePromptStr
}
promptWaitAttempts++
console.log('[轮询] 分镜图已生成但 videoPrompt 未就绪,继续轮询', { promptWaitAttempts })
pollIntervalId.value = setTimeout(poll, 5000)
return
}
// videoPrompt 已就绪,可以停止轮询并提示
if (pollIntervalId.value) {
clearTimeout(pollIntervalId.value)
pollIntervalId.value = null
}
currentStep.value = 'video'
promptWaitAttempts = 0
setTimeout(() => {
ElMessage.success(t('video.storyboard.storyboardCompleted'))
}, 100)
@@ -2719,14 +2734,29 @@ const checkLastTaskStatus = async () => {
onMounted(async () => {
// 处理"做同款"传递的路由参数
if (route.query.prompt) {
inputText.value = route.query.prompt
if (route.query.prompt || route.query.referenceImage) {
console.log('[做同款] 接收参数:', route.query)
if (route.query.prompt) {
inputText.value = route.query.prompt
}
if (route.query.aspectRatio) {
aspectRatio.value = route.query.aspectRatio
}
if (route.query.duration) {
duration.value = route.query.duration
}
// 处理参考图
if (route.query.referenceImage) {
uploadedImages.value = [{
url: route.query.referenceImage,
file: null,
name: '参考图片'
}]
console.log('[做同款] 设置参考图:', route.query.referenceImage)
}
ElMessage.success(t('video.storyboardVideo.historyParamsFilled') || '已填充历史参数')
// 清除URL中的query参数避免刷新页面重复填充
router.replace({ path: route.path })
@@ -3744,6 +3774,11 @@ onBeforeUnmount(() => {
font-weight: 500;
}
.tip-item.points {
color: #fbbf24;
font-weight: 500;
}
.optimize-btn {
display: flex;
justify-content: flex-end;
@@ -3883,7 +3918,6 @@ onBeforeUnmount(() => {
background: #6b7280;
cursor: not-allowed;
transform: none;
box-shadow: none;
opacity: 0.6;
}
@@ -3892,6 +3926,21 @@ onBeforeUnmount(() => {
box-shadow: none;
}
.btn-points {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 8px;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
font-size: 14px;
}
.btn-points .el-icon {
color: #60a5fa;
}
.login-tip-floating {
margin-top: 12px;
text-align: center;
@@ -3963,7 +4012,6 @@ onBeforeUnmount(() => {
.preview-content {
flex: 1;
background: #1a1a1a;
border: 2px solid #2a2a2a;
border-radius: 12px;
@@ -3971,14 +4019,14 @@ onBeforeUnmount(() => {
align-items: center;
justify-content: center;
transition: all 0.2s ease;
overflow: hidden; /* 防止内容溢出 */
min-height: 0; /* 允许flex子项缩小 */
position: relative; /* 为绝对定位的子元素提供定位参考 */
overflow: hidden;
position: relative;
width: 80%;
max-width: 1000px;
margin-left: 0;
margin-right: auto;
align-self: flex-start;
min-height: 300px;
padding: 24px;
margin-bottom: 20px;
box-sizing: border-box;
}
.preview-content:hover {
@@ -3987,13 +4035,10 @@ onBeforeUnmount(() => {
.preview-placeholder {
width: 100%;
min-height: 200px;
background: #1a1a1a;
border-radius: 8px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
}
.placeholder-text {
@@ -4212,15 +4257,15 @@ onBeforeUnmount(() => {
/* 任务状态样式 */
.task-status {
background: rgba(255, 255, 255, 0.05);
background: #1a1a1a;
border: 2px solid #2a2a2a;
border-radius: 12px;
padding: 20px;
padding: 24px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
width: 80%;
max-width: 1000px;
margin-left: 0;
margin-right: auto;
min-height: 300px;
box-sizing: border-box;
}
.status-header {
@@ -4243,18 +4288,14 @@ onBeforeUnmount(() => {
font-family: monospace;
}
/* 任务描述样式 */
/* 任务描述样式 - 和历史记录提示词一致 */
.task-description {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 16px;
margin: 15px 0;
color: #e5e7eb;
font-size: 14px;
line-height: 1.6;
color: #e5e7eb;
border: 1px solid rgba(255, 255, 255, 0.1);
max-height: 120px;
overflow-y: auto;
margin-bottom: 16px;
white-space: pre-wrap;
word-wrap: break-word;
}
/* 视频预览容器 */
@@ -4636,10 +4677,8 @@ onBeforeUnmount(() => {
border: none;
padding: 0;
transition: all 0.2s ease;
width: 80%;
max-width: 1000px;
margin-left: 0;
margin-right: auto;
width: 100%;
max-width: none;
}
.history-status-checkbox {
@@ -4688,10 +4727,13 @@ onBeforeUnmount(() => {
margin-bottom: 16px;
white-space: pre-wrap;
word-wrap: break-word;
width: 80%;
max-width: 1000px;
}
.history-preview {
width: 100%;
width: 80%;
max-width: 1000px;
aspect-ratio: 16/9;
background: #000;
border-radius: 8px;