فهرست منبع

1、调整任务卡片出场逻辑
2、优化语音收音样式,统一使用语音输入并支持键盘更改(此版本未处理收音问题)

liyanbo 2 ماه پیش
والد
کامیت
a0653527f4
1فایلهای تغییر یافته به همراه123 افزوده شده و 143 حذف شده
  1. 123 143
      src/views/AIPage/aiGenerate/DialogContent.vue

+ 123 - 143
src/views/AIPage/aiGenerate/DialogContent.vue

@@ -30,24 +30,24 @@
     <!-- 内容区域 -->
     <div class="content-box">
       <!-- 人物形象 -->
-      <div 
-        v-if="currentDialogue && !isInputCardVisible"
+      <div
+        v-if="currentDialogue && (currentDialogue.type === 'digital' || currentDialogue.type === 'quest')"
         :key="`character-${currentDialogueIndex}`"
-        class="character" 
-        :class="{ 
-          'left': getCharacterSide(currentDialogue.roleName) === 'left', 
+        class="character"
+        :class="{
+          'left': getCharacterSide(currentDialogue.roleName) === 'left',
           'right': getCharacterSide(currentDialogue.roleName) === 'right'
         }"
         :style="{ backgroundImage: `url(${getCharacterImage(currentDialogue.roleName)})` }"
       ></div>
-      
+
       <!-- 对话卡片 -->
-      <div 
-        v-if="currentDialogue && activeInputMode !== 'keyboard' && !isInputCardVisible"
+      <div
+        v-if="currentDialogue && (currentDialogue.type === 'digital' || currentDialogue.type === 'quest')"
         :key="`dialogue-${currentDialogueIndex}`"
-        class="dialogue-card" 
-        :class="{ 
-          'left': getCharacterSide(currentDialogue.roleName) === 'left', 
+        class="dialogue-card"
+        :class="{
+          'left': getCharacterSide(currentDialogue.roleName) === 'left',
           'right': getCharacterSide(currentDialogue.roleName) === 'right'
         }"
       >
@@ -56,68 +56,38 @@
         </div>
         <div class="dialogue-content" v-html="parseMarkdown(currentDialogue.content)"></div>
       </div>
-      
+
       <!-- 用户输入卡片 -->
-      <div 
-        v-if="isInputCardVisible && (activeInputMode === 'keyboard' || activeInputMode === 'voice')"
+      <div
+        v-if="currentDialogue.type == 'user'"
         class="dialogue-card user-input-card"
       >
         <div class="dialogue-header">
           <span class="role-name">我</span>
         </div>
         <div class="dialogue-content">
-          <!-- 键盘输入内容 -->
-          <template v-if="activeInputMode === 'keyboard'">
-            <textarea 
-              v-model="userInput" 
-              class="user-input-textarea" 
-              placeholder="请输入内容..."
-              @keyup.enter.exact="submitUserInput"
-            ></textarea>
-            <div class="input-actions">
-              <button class="cancel-btn" @click="cancelUserInput">取消</button>
-              <button class="submit-btn" @click="submitUserInput">发送</button>
-            </div>
-          </template>
-          
-          <!-- 语音输入内容 -->
-          <template v-else-if="activeInputMode === 'voice'">
-            <div class="voice-input-content" v-if="voiceInput">
-              {{ voiceInput }}
-            </div>
-            <div class="voice-input-placeholder" v-else>
-              点击麦克风开始语音输入...
-            </div>
-            <div class="input-actions">
-              <button class="cancel-btn" @click="cancelUserInput">取消</button>
-              <button class="submit-btn" @click="submitVoiceInput">发送</button>
-            </div>
-          </template>
+          <textarea
+            v-model="userInput"
+            class="user-input-textarea"
+            placeholder="请输入内容..."
+            @keyup.enter.exact="submitUserInput"
+          ></textarea>
+          <div class="input-actions">
+            <button class="cancel-btn" @click="cancelUserInput">清空</button>
+            <button class="submit-btn" @click="submitUserInput">发送</button>
+          </div>
         </div>
       </div>
-      
+
       <!-- 输入按钮区域 -->
-      <div class="input-buttons-container">
+      <div class="input-buttons-container" v-if="currentDialogue.type == 'user'">
         <!-- 语音输入按钮 -->
-        <div 
-          class="voice-input-outer"
-          :class="{ 'active': activeInputMode === 'voice', 'inactive': activeInputMode === 'keyboard' }"
-        >
+        <div class="voice-input-outer" :class="{ 'recording': isVoiceRecording }">
           <VoiceInput 
             @voiceRecognized="handleVoiceRecognized" 
             @recordingStatusChanged="handleRecordingStatusChanged"
-            @click="toggleVoiceInput"
           />
         </div>
-        <!-- 键盘输入按钮 -->
-        <div 
-          class="keyboard-input-outer"
-          :class="{ 'active': activeInputMode === 'keyboard', 'inactive': activeInputMode === 'voice' }"
-        >
-          <div class="keyboard-btn" @click="toggleKeyboardInput">
-            <el-icon class="keyboard-icon"><Grid /></el-icon>
-          </div>
-        </div>
       </div>
     </div>
 
@@ -160,14 +130,12 @@ const currentSectionIndex = ref(0)
 const currentDialogueIndex = ref(0)
 // 是否正在播放
 const isPlaying = ref(false)
-// 当前激活的输入模式:'voice' 或 'keyboard'
-const activeInputMode = ref('voice')
 // 输入卡片是否可见
 const isInputCardVisible = ref(false)
 // 用户输入内容
 const userInput = ref('')
-// 语音输入内容
-const voiceInput = ref('')
+// 语音录音状态
+const isVoiceRecording = ref(false)
 
 // 音频对象
 // 背景音频
@@ -203,38 +171,36 @@ const md = new MarkdownIt({
 // 方法
 const handleVoiceRecognized = (text) => {
   console.log('语音识别结果:', text)
-  voiceInput.value = text
+  // 获取输入框元素
+  const textarea = document.querySelector('.user-input-textarea')
+  if (textarea) {
+    // 获取光标位置
+    const startPos = textarea.selectionStart
+    const endPos = textarea.selectionEnd
+    // 在光标位置插入文本
+    userInput.value = userInput.value.substring(0, startPos) + text + userInput.value.substring(endPos)
+    // 重新设置光标位置到插入文本的末尾
+    setTimeout(() => {
+      textarea.selectionStart = textarea.selectionEnd = startPos + text.length
+    }, 0)
+  } else {
+    // 如果没有找到输入框,直接替换整个内容
+    userInput.value = text
+  }
 }
 
 // 处理录音状态变化
 const handleRecordingStatusChanged = (isRecording) => {
   console.log('录音状态:', isRecording)
-}
-
-// 切换键盘输入
-const toggleKeyboardInput = () => {
-  console.log('切换键盘输入')
-  activeInputMode.value = 'keyboard'
-  isInputCardVisible.value = true
+  isVoiceRecording.value = isRecording
 }
 
 // 提交用户输入
-const submitUserInput = () => {
+const submitUserInput = async () => {
   if (userInput.value.trim()) {
     console.log('用户输入:', userInput.value)
-    // 提交后隐藏输入卡片
-    isInputCardVisible.value = false
-    userInput.value = ''
-  }
-}
-
-// 提交语音输入
-const submitVoiceInput = async () => {
-  if (voiceInput.value.trim()) {
-    console.log('语音输入:', voiceInput.value)
-
+    
     await createAiChart();
-
     await doSendMessage();
   }
 }
@@ -243,14 +209,6 @@ const submitVoiceInput = async () => {
 const cancelUserInput = () => {
   isInputCardVisible.value = false
   userInput.value = ''
-  voiceInput.value = ''
-}
-
-// 切换语音输入
-const toggleVoiceInput = () => {
-  console.log('切换语音输入')
-  activeInputMode.value = 'voice'
-  isInputCardVisible.value = true
 }
 
 // 解析 Markdown 内容
@@ -475,7 +433,7 @@ const createAiChart = async () => {
 /** 真正执行【发送】消息操作 */
 const doSendMessage = async () => {
   // 校验
-  if (voiceInput.value.length < 1) {
+  if (userInput.value.length < 1) {
     console.error('发送失败,原因:内容为空!')
     return
   }
@@ -485,15 +443,16 @@ const doSendMessage = async () => {
     return
   }
 
-  // 清空输入框
-  voiceInput.value = ''
-
   // 执行发送
   await doSendMessageStream({
     conversationId: activeConversationId.value,
-    content: voiceInput.value,
+    content: userInput.value,
     contentAnswer: null,
   })
+  
+  // 清空输入框
+  userInput.value = ''
+  isInputCardVisible.value = false
 }
 
 /** 真正执行【发送】消息操作 */
@@ -733,7 +692,7 @@ onUnmounted(() => {
   display: flex;
   align-items: flex-end;
   justify-content: center;
-  padding-bottom: rpx(20);
+  padding-bottom: rpx(10);
   z-index: 10;
 }
 
@@ -888,7 +847,7 @@ onUnmounted(() => {
   position: absolute;
   left: 50%;
   transform: translateX(-50%);
-  bottom: rpx(50);
+  bottom: rpx(70);
   right: auto;
   animation: dialogueEnterCenter 0.6s ease forwards;
 }
@@ -1060,61 +1019,50 @@ onUnmounted(() => {
 }
 
 .voice-input-outer {
-  width: rpx(30);
-  height: rpx(30);
+  position: relative;
+  width: rpx(50);
+  height: rpx(50);
   border-radius: 50%;
   background: transparent;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 
-  &.active {
-    width: rpx(40);
-    height: rpx(40);
-    left: 50%;
-    transform: translateX(-50%);
-    z-index: 11;
-
-    :deep(.voice-input-container) {
-      .speech-btn {
-        width: rpx(36);
-        height: rpx(36);
-        border: rpx(2) solid rgba(0, 100, 192);
-        box-shadow: 0 rpx(3) rpx(8) rgba(0, 0, 0, 0.3);
-
-        .el-icon {
-          font-size: rpx(18);
-        }
-      }
-    }
+  // 正常状态只显示一圈
+  &::before {
+    content: '';
+    position: absolute;
+    width: 85%;
+    height: 85%;
+    border-radius: 50%;
+    border: rpx(2) solid rgba(80, 190, 240, 0.6);
   }
 
-  &.inactive {
-    width: rpx(24);
-    height: rpx(24);
-    left: calc(50% - rpx(40));
-    transform: translateX(-50%);
-    z-index: 10;
+  // 录音时显示波纹效果
+  &.recording {
+    &::before {
+      animation: pulse 2s infinite;
+    }
 
-    :deep(.voice-input-container) {
-      .speech-btn {
-        width: rpx(22);
-        height: rpx(22);
-        border: rpx(1) solid rgba(128, 128, 128, 0.5);
-        box-shadow: none;
-        background: linear-gradient(135deg, #E0E0E0, #C0C0C0);
-
-        .el-icon {
-          font-size: rpx(12);
-          color: #808080;
-        }
-      }
+    &::after {
+      content: '';
+      position: absolute;
+      width: 85%;
+      height: 85%;
+      border-radius: 50%;
+      border: rpx(2) solid rgba(80, 190, 240, 0.4);
+      animation: pulse 2s infinite 0.5s;
     }
   }
 
   :deep(.voice-input-container) {
+    position: relative;
+    z-index: 10;
     .speech-btn {
-      width: rpx(30);
-      height: rpx(30);
+      width: rpx(40);
+      height: rpx(40);
       border-radius: 50%;
-      border: rpx(1) solid rgba(0, 100, 192);
+      border: rpx(2) solid rgba(0, 100, 192);
       background: linear-gradient(135deg, #A0DCF0, #50BEF0);
       display: flex;
       align-items: center;
@@ -1123,19 +1071,40 @@ onUnmounted(() => {
       gap: 0;
       transition: all 0.3s ease;
       cursor: pointer;
+      position: relative;
+      overflow: hidden;
 
       &:hover {
-        transform: scale(1.1);
-        box-shadow: 0 rpx(2) rpx(8) rgba(0, 0, 0, 0.3);
+        transform: scale(1.05);
+        box-shadow: 0 rpx(4) rpx(12) rgba(0, 0, 0, 0.3);
       }
 
       &:active {
         transform: scale(0.95);
       }
 
+      &::before {
+        content: '';
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        width: 0;
+        height: 0;
+        border-radius: 50%;
+        background: rgba(255, 255, 255, 0.5);
+        transform: translate(-50%, -50%);
+        transition: width 0.6s, height 0.6s;
+      }
+
+      &:active::before {
+        width: rpx(80);
+        height: rpx(80);
+      }
+
       .el-icon {
-        font-size: rpx(15);
+        font-size: rpx(20);
         color: #0064BE;
+        z-index: 1;
       }
 
       .countdown-text {
@@ -1149,6 +1118,17 @@ onUnmounted(() => {
   }
 }
 
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(1.15);
+    opacity: 0;
+  }
+}
+
 .keyboard-input-outer {
   width: rpx(30);
   height: rpx(30);