Explorar el Código

feat(photography): 实现新的进度条组件和逻辑

- 在 detail.vue 中新增 useNewProgress 和 progressSteps 数据属性
- 添加 updateProgressStep 和 initProgressSteps 方法用于管理进度步骤
- 修改 LoadingDialog 组件以支持新的进度条显示方式
- 新增 ProgressSteps.vue 组件实现分步进度展示
- 调整加载对话框宽度以适应新进度条布局
- 优化进度消息处理逻辑,增加对空数组的判断
-修复请求状态更新逻辑错误,确保正确反映处理状态
panqiuyao hace 1 mes
padre
commit
7dde098245

+ 26 - 4
frontend/src/views/Photography/components/LoadingDialog.vue

@@ -4,13 +4,21 @@
     :show-close="requesting"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
-    width="600px"
+    width="1000px"
     custom-class="loading-dialog-EL"
     align-center
     append-to-body
   >
     <div class="loading-content mar-top-10">
-      <div class="progress-container">
+      <!-- 新的步骤进度条 -->
+      <ProgressSteps
+        v-if="useNewProgress && progressSteps.length > 0"
+        :steps="progressSteps"
+        @complete="handleButtonClick"
+      />
+
+      <!-- 原有的简单进度条 -->
+      <div v-else class="progress-container">
         <div class="progress-bar">
           <div
             class="progress-inner"
@@ -27,7 +35,7 @@
       <slot name="progressMessages"></slot>
 
       <el-button
-        v-if="!disabledButton"
+        v-if="!disabledButton && !useNewProgress"
         :disabled="disabledButton"
         type="primary"
         class="action-button   button--primary1  mar-top-20"
@@ -41,6 +49,16 @@
 
 <script setup lang="ts">
 import { ref, defineProps, defineEmits , watch } from 'vue'
+import ProgressSteps from './ProgressSteps.vue'
+
+interface StepData {
+  msg_type: string
+  name: string
+  status: '等待处理' | '正在处理' | '处理完成' | '处理失败'
+  current: number
+  total: number
+  error: number
+}
 
 interface Props {
   modelValue: boolean
@@ -49,6 +67,8 @@ interface Props {
   disabledButton?: boolean
   buttonText?: string,
   requesting?: boolean
+  useNewProgress?: boolean
+  progressSteps?: StepData[]
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -56,7 +76,9 @@ const props = withDefaults(defineProps<Props>(), {
   message: '正在为您处理,请稍后...',
   disabledButton: true,
   buttonText: '处理完毕,点击打开最终图片目录',
-  requesting: false
+  requesting: false,
+  useNewProgress: false,
+  progressSteps: () => []
 })
 
 const emit = defineEmits<{

+ 323 - 0
frontend/src/views/Photography/components/ProgressSteps.vue

@@ -0,0 +1,323 @@
+<template>
+  <div class="progress-steps">
+    <div class="steps-container">
+      <div 
+        v-for="(step, index) in steps" 
+        :key="step.msg_type" 
+        class="step-item"
+        :class="getStepClass(step)"
+      >
+        <!-- 步骤圆圈 -->
+        <div class="step-circle">
+          <div v-if="step.status === '处理完成'" class="step-icon completed">
+            <el-icon><Check /></el-icon>
+          </div>
+          <div v-else-if="step.status === '正在处理'" class="step-icon processing">
+            <div class="loading-spinner"></div>
+          </div>
+          <div v-else-if="step.status === '处理失败'" class="step-icon failed">
+            <el-icon><Close /></el-icon>
+          </div>
+          <div v-else class="step-icon waiting">
+            {{ index + 1 }}
+          </div>
+        </div>
+
+        <!-- 步骤内容 -->
+        <div class="step-content">
+          <div class="step-title">{{ getStepTitle(step) }}</div>
+          <div class="step-description">{{ step.name }}</div>
+          <div v-if="step.status === '正在处理'" class="step-progress">
+            <div class="progress-bar">
+              <div 
+                class="progress-fill" 
+                :style="{ width: getProgressPercentage(step) + '%' }"
+              ></div>
+            </div>
+            <span class="progress-text">{{ step.current }}/{{ step.total }}</span>
+          </div>
+        </div>
+
+        <!-- 连接线 -->
+        <div v-if="index < steps.length - 1" class="step-connector"></div>
+      </div>
+    </div>
+
+    <!-- 完成按钮 -->
+    <div v-if="allStepsCompleted" class="completion-section">
+      <el-button 
+        type="primary" 
+        class="completion-button"
+        @click="handleComplete"
+      >
+        全部处理完毕,点击打开最终图片目录
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue'
+import { Check, Close } from '@element-plus/icons-vue'
+
+interface StepData {
+  msg_type: string
+  name: string
+  status: '等待处理' | '正在处理' | '处理完成' | '处理失败'
+  current: number
+  total: number
+  error: number
+}
+
+interface Props {
+  steps: StepData[]
+  onComplete?: () => void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  steps: () => [],
+  onComplete: () => {}
+})
+
+const emit = defineEmits<{
+  (e: 'complete'): void
+}>()
+
+// 步骤标题映射
+const stepTitleMap: Record<string, string> = {
+  'segment_progress': '处理完成',
+  'scene_progress': '正在处理', 
+  'upper_footer_progress': '等待处理',
+  'detail_progress': '等待处理'
+}
+
+// 获取步骤标题
+const getStepTitle = (step: StepData) => {
+  return stepTitleMap[step.msg_type] || step.status
+}
+
+// 获取步骤样式类
+const getStepClass = (step: StepData) => {
+  return {
+    'step-completed': step.status === '处理完成',
+    'step-processing': step.status === '正在处理',
+    'step-failed': step.status === '处理失败',
+    'step-waiting': step.status === '等待处理'
+  }
+}
+
+// 获取进度百分比
+const getProgressPercentage = (step: StepData) => {
+  if (step.total === 0) return 0
+  return Math.round((step.current / step.total) * 100)
+}
+
+// 检查是否所有步骤都完成
+const allStepsCompleted = computed(() => {
+  return props.steps.every(step => step.status === '处理完成')
+})
+
+// 处理完成按钮点击
+const handleComplete = () => {
+  emit('complete')
+  props.onComplete()
+}
+
+// 监听步骤变化
+watch(() => props.steps, (newSteps) => {
+  console.log('步骤更新:', newSteps)
+}, { deep: true })
+</script>
+
+<style lang="scss" scoped>
+.progress-steps {
+  width: 100%;
+  padding: 20px 0;
+}
+
+.steps-container {
+  display: flex;
+  flex-direction: row;
+  gap: 20px;
+  position: relative;
+  align-items: flex-start;
+  justify-content: space-between;
+  padding: 0 20px;
+}
+
+.step-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+  padding: 15px 10px;
+  flex: 1;
+  min-width: 0;
+}
+
+.step-circle {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 15px;
+  flex-shrink: 0;
+  position: relative;
+  z-index: 2;
+}
+
+.step-icon {
+  width: 100%;
+  height: 100%;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: bold;
+  color: white;
+  font-size: 16px;
+}
+
+.step-icon.completed {
+  background: linear-gradient(135deg, #67C23A, #85CE61);
+}
+
+.step-icon.processing {
+  background: linear-gradient(135deg, #409EFF, #66B1FF);
+  position: relative;
+}
+
+.step-icon.failed {
+  background: linear-gradient(135deg, #F56C6C, #F78989);
+}
+
+.step-icon.waiting {
+  background: linear-gradient(135deg, #C0C4CC, #DCDFE6);
+  color: #909399;
+}
+
+.loading-spinner {
+  width: 20px;
+  height: 20px;
+  border: 2px solid rgba(255, 255, 255, 0.3);
+  border-top: 2px solid white;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.step-content {
+  flex: 1;
+  min-width: 0;
+  text-align: center;
+  width: 100%;
+}
+
+.step-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 5px;
+}
+
+.step-description {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 8px;
+}
+
+.step-progress {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  margin-top: 8px;
+}
+
+.progress-bar {
+  width: 100%;
+  height: 6px;
+  background: #E4E7ED;
+  border-radius: 3px;
+  overflow: hidden;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #2FB0FF, #B863FB);
+  border-radius: 3px;
+  transition: width 0.3s ease;
+}
+
+.progress-text {
+  font-size: 12px;
+  color: #909399;
+  min-width: 40px;
+}
+
+.step-connector {
+  position: absolute;
+  top: 20px;
+  left: calc(100% + 10px);
+  width: 20px;
+  height: 2px;
+  background: #E4E7ED;
+  z-index: 1;
+}
+
+.step-item:last-child .step-connector {
+  display: none;
+}
+
+.completion-section {
+  margin-top: 30px;
+  text-align: center;
+}
+
+.completion-button {
+  background: linear-gradient(135deg, #2FB0FF, #B863FB);
+  border: none;
+  padding: 12px 30px;
+  font-size: 16px;
+  font-weight: 600;
+  border-radius: 25px;
+  box-shadow: 0 4px 15px rgba(47, 176, 255, 0.3);
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 6px 20px rgba(47, 176, 255, 0.4);
+  }
+}
+
+// 步骤状态样式
+.step-completed {
+  .step-title {
+    color: #67C23A;
+  }
+}
+
+.step-processing {
+  .step-title {
+    color: #409EFF;
+  }
+}
+
+.step-failed {
+  .step-title {
+    color: #F56C6C;
+  }
+}
+
+.step-waiting {
+  .step-title {
+    color: #909399;
+  }
+}
+</style>

+ 97 - 4
frontend/src/views/Photography/detail.vue

@@ -295,7 +295,7 @@
 
 
   <loading-dialog v-if="loadingDialogVisible" v-model="loadingDialogVisible" :requesting="requesting" :progress="progress" :message="message"
-    :disabled-button="disabledButton" @button-click="handleComplete">
+    :disabled-button="disabledButton" :use-new-progress="useNewProgress" :progress-steps="progressSteps" @button-click="handleComplete">
     <template v-if="partErrList && partErrList.length > 0" #errList>
       <div v-for="(item, idx) in partErrList" :key="idx">
         <span v-if="item.goods_art_no">{{ item.goods_art_no }}:</span><span>{{ item.info }}</span>
@@ -316,7 +316,7 @@
           <div v-for="(msg, index) in progressMessages" :key="index" class="message-item flex left">
             <div class="message-time">{{ formatTime(msg.timestamp) }}</div>
             <div class="message-content mar-left-10" v-if="msg">
-              <span class="goods-no" v-if="msg.goods_art_nos">货号{{ msg.goods_art_nos.join(', ') }}:</span>
+              <span class="goods-no" v-if="msg.goods_art_nos && msg.goods_art_nos.length > 0">货号{{ msg.goods_art_nos.join(', ') }}:</span>
               <span class="message-text">{{ msg.msg }}</span>
             </div>
           </div>
@@ -406,6 +406,67 @@ const loadingDialogVisible = ref(false)
 const progress = ref(0)
 const message = ref('正在为您处理,请稍后')
 const disabledButton = ref(true)
+
+// 新的进度条相关数据
+const useNewProgress = ref(false)
+const progressSteps = ref<Array<{
+  msg_type: string
+  name: string
+  status: '等待处理' | '正在处理' | '处理完成' | '处理失败'
+  current: number
+  total: number
+  error: number
+}>>([])
+
+// 更新进度步骤
+const updateProgressStep = (msgType: string, stepData: any) => {
+  console.log('updateProgressStep called:', msgType, stepData)
+  const stepIndex = progressSteps.value.findIndex(step => step.msg_type === msgType)
+  if (stepIndex !== -1) {
+    // 更新现有步骤 - 使用新数组来确保响应式更新
+    const newSteps = [...progressSteps.value]
+    newSteps[stepIndex] = {
+      ...newSteps[stepIndex],
+      name: stepData.name, // 保持原有名称或使用新名称
+      status: stepData.status,
+      current: stepData.current || 0,
+      total: stepData.total || 0,
+      error: stepData.error || 0
+    }
+    progressSteps.value = newSteps
+    console.log('Updated step:', newSteps[stepIndex])
+  } else {
+    // 添加新步骤
+    progressSteps.value = [...progressSteps.value, {
+      msg_type: msgType,
+      name: stepData.name,
+      status: stepData.status,
+      current: stepData.current || 0,
+      total: stepData.total || 0,
+      error: stepData.error || 0
+    }]
+    console.log('Added new step:', progressSteps.value[progressSteps.value.length - 1])
+  }
+}
+
+// 获取步骤名称
+// 初始化进度步骤 - 从后端数据动态获取
+const initProgressSteps = (backendSteps?: any[]) => {
+  if (backendSteps && backendSteps.length > 0) {
+    // 使用后端返回的步骤数据
+    progressSteps.value = backendSteps.map(step => ({
+      msg_type: step.msg_type,
+      name: step.name,
+      status: '等待处理',
+      current: 0,
+      total: step.total || 0,
+      error: 0
+    }))
+  } else {
+    // 默认步骤(当后端没有返回步骤数据时使用)
+    progressSteps.value = []
+  }
+}
 // 进度消息队列
 const progressMessages = ref<Array<{
   goods_no: string
@@ -757,6 +818,12 @@ const handleProgressMessage = (data: any) => {
     // 更新当前显示的消息
     message.value = data.data.goods_art_nos ? `货号${data.data.goods_art_nos.join(', ')}:${data.msg}` : `${data.msg}`
     scrollMessageListToBottom()
+
+    // 更新新的进度条
+
+    if (data.progress) {
+      updateProgressStep('detail_progress', data.progress)
+    }
   }
 }
 
@@ -777,6 +844,11 @@ const handleSegmentProgressMessage = (data: any) => {
     // 更新当前显示的消息
     message.value = data.data.goods_art_nos ? `货号${data.data.goods_art_nos.join(', ')}:${data.msg}` : `${data.msg}`
     scrollMessageListToBottom()
+
+    // 更新新的进度条
+    if (data.progress) {
+      updateProgressStep('segment_progress', data.progress)
+    }
   }
 }
 
@@ -795,6 +867,11 @@ const handleUpperFooterProgressMessage = (data: any) => {
     progressMessages.value.push(messageData)
     message.value = data.data.goods_art_nos ? `货号${data.data.goods_art_nos.join(', ')}:${data.msg}` : `${data.msg}`
     scrollMessageListToBottom()
+
+    // 更新新的进度条
+    if (data.progress) {
+      updateProgressStep('upper_footer_progress', data.progress)
+    }
   }
 }
 
@@ -813,6 +890,11 @@ const handleSceneProgressMessage = (data: any) => {
     progressMessages.value.push(messageData)
     message.value = data.data.goods_art_nos ? `货号${data.data.goods_art_nos.join(', ')}:${data.msg}` : `${data.msg}`
     scrollMessageListToBottom()
+
+    // 更新新的进度条
+    if (data.progress) {
+      updateProgressStep('scene_progress', data.progress)
+    }
   }
 }
 
@@ -831,6 +913,10 @@ const handleUploadGoodsProgressMessage = (data: any) => {
     progressMessages.value.push(messageData)
     message.value = data.data.goods_art_nos ? `货号${data.data.goods_art_nos.join(', ')}:${data.msg}` : `${data.msg}`
     scrollMessageListToBottom()
+    // 更新新的进度条
+    if (data.progress) {
+      updateProgressStep('upload_goods_progress', data.progress)
+    }
   }
 }
 
@@ -959,9 +1045,10 @@ const generate = async function () {
   }
 
   // 先进行检测
+  let checkResult;
   try {
    // ElMessage.info('正在检测参数,请稍候...');
-    await checkParams();
+    checkResult = await checkParams();
    // ElMessage.success('检测通过,开始生成...');
   } catch (error) {
     ElMessage.error(error.message || '检测失败,请检查参数');
@@ -1017,6 +1104,12 @@ const generate = async function () {
   progressMessages.value = []
   showMessageHistory.value = true
 
+  // 启用新的进度条并初始化步骤
+  useNewProgress.value = true
+  // 从检测结果中获取步骤信息
+  const backendSteps = checkResult?.data?.progress || []
+  initProgressSteps(backendSteps)
+
   openLoadingDialog(goods_art_nos.value.length * 10)
   clientStore.ipc.removeAllListeners(icpList.socket.message + '_detail_result_progress');
   clientStore.ipc.send(icpList.generate.generatePhotoDetail, params);
@@ -1155,7 +1248,7 @@ const generate = async function () {
       message.value = '全部货号生成失败'
 
     }
-    requesting.value = false
+    requesting.value = true
   });
 }
 const openLoadingDialog = (timer: number) => {