Эх сурвалжийг харах

feat: 按说说话交互样式

sunxiao 2 өдөр өмнө
parent
commit
51040e7e45

+ 2 - 1
.env.development

@@ -8,4 +8,5 @@ VITE_APP_ENV = 'development'
 VITE_IMGAGE_PATH = 'https://static.fuxicarbon.com/bigModel/wechat'
 
 # 开发环境
-VITE_APP_BASE_API =  http://192.168.40.18:8080
+VITE_APP_BASE_API =  http://chat.sequoialibra.com:81/apiServe
+# VITE_APP_BASE_API =  http://192.168.40.18:8080

+ 296 - 0
src/assets/style/common.scss

@@ -8,4 +8,300 @@
 
 .chat-container {
   padding: 0 20rpx;
+}
+
+
+.la-ball-running-dots,
+.la-ball-running-dots>.item {
+  position: relative;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+.la-ball-running-dots {
+  font-size: 0;
+  color: #fff;
+}
+
+.la-ball-running-dots.la-dark {
+  color: #333;
+}
+
+.la-ball-running-dots>.item {
+  display: inline-block;
+  float: none;
+  background-color: currentColor;
+  border: 0 solid currentColor;
+}
+
+.la-ball-running-dots {
+  width: 10px;
+  height: 10px;
+}
+
+.la-ball-running-dots>.item {
+  position: absolute;
+  width: 20rpx;
+  height: 20rpx;
+  margin-left: -25px;
+  border-radius: 100%;
+  -webkit-animation: ball-running-dots-animate 2s linear infinite;
+  -moz-animation: ball-running-dots-animate 2s linear infinite;
+  -o-animation: ball-running-dots-animate 2s linear infinite;
+  animation: ball-running-dots-animate 2s linear infinite;
+}
+
+.la-ball-running-dots>.item:nth-child(1) {
+  -webkit-animation-delay: 0s;
+  -moz-animation-delay: 0s;
+  -o-animation-delay: 0s;
+  animation-delay: 0s;
+}
+
+.la-ball-running-dots>.item:nth-child(2) {
+  -webkit-animation-delay: -.4s;
+  -moz-animation-delay: -.4s;
+  -o-animation-delay: -.4s;
+  animation-delay: -.4s;
+}
+
+.la-ball-running-dots>.item:nth-child(3) {
+  -webkit-animation-delay: -.8s;
+  -moz-animation-delay: -.8s;
+  -o-animation-delay: -.8s;
+  animation-delay: -.8s;
+}
+
+.la-ball-running-dots>.item:nth-child(4) {
+  -webkit-animation-delay: -1.2s;
+  -moz-animation-delay: -1.2s;
+  -o-animation-delay: -1.2s;
+  animation-delay: -1.2s;
+}
+
+.la-ball-running-dots>.item:nth-child(5) {
+  -webkit-animation-delay: -1.6s;
+  -moz-animation-delay: -1.6s;
+  -o-animation-delay: -1.6s;
+  animation-delay: -1.6s;
+}
+
+.la-ball-running-dots>.item:nth-child(6) {
+  -webkit-animation-delay: -2s;
+  -moz-animation-delay: -2s;
+  -o-animation-delay: -2s;
+  animation-delay: -2s;
+}
+
+.la-ball-running-dots>.item:nth-child(7) {
+  -webkit-animation-delay: -2.4s;
+  -moz-animation-delay: -2.4s;
+  -o-animation-delay: -2.4s;
+  animation-delay: -2.4s;
+}
+
+.la-ball-running-dots>.item:nth-child(8) {
+  -webkit-animation-delay: -2.8s;
+  -moz-animation-delay: -2.8s;
+  -o-animation-delay: -2.8s;
+  animation-delay: -2.8s;
+}
+
+.la-ball-running-dots>.item:nth-child(9) {
+  -webkit-animation-delay: -3.2s;
+  -moz-animation-delay: -3.2s;
+  -o-animation-delay: -3.2s;
+  animation-delay: -3.2s;
+}
+
+.la-ball-running-dots>.item:nth-child(10) {
+  -webkit-animation-delay: -3.6s;
+  -moz-animation-delay: -3.6s;
+  -o-animation-delay: -3.6s;
+  animation-delay: -3.6s;
+}
+
+.la-ball-running-dots.la-sm {
+  width: 8rpx;
+  height: 8rpx;
+}
+
+.la-ball-running-dots.la-sm>.item {
+  width: 8rpx;
+  height: 8rpx;
+  margin-left: -12px;
+}
+
+.la-ball-running-dots.la-2x {
+  width: 20px;
+  height: 20px;
+}
+
+.la-ball-running-dots.la-2x>.item {
+  width: 20px;
+  height: 20px;
+  margin-left: -50px;
+}
+
+.la-ball-running-dots.la-3x {
+  width: 30px;
+  height: 30px;
+}
+
+.la-ball-running-dots.la-3x>.item {
+  width: 30px;
+  height: 30px;
+  margin-left: -75px;
+}
+
+/*
+ * Animation
+ */
+@-webkit-keyframes ball-running-dots-animate {
+
+  0%,
+  100% {
+    width: 100%;
+    height: 100%;
+    -webkit-transform: translateY(0) translateX(500%);
+    transform: translateY(0) translateX(500%);
+  }
+
+  80% {
+    -webkit-transform: translateY(0) translateX(0);
+    transform: translateY(0) translateX(0);
+  }
+
+  85% {
+    width: 100%;
+    height: 100%;
+    -webkit-transform: translateY(-125%) translateX(0);
+    transform: translateY(-125%) translateX(0);
+  }
+
+  90% {
+    width: 200%;
+    height: 75%;
+  }
+
+  95% {
+    width: 100%;
+    height: 100%;
+    -webkit-transform: translateY(-100%) translateX(500%);
+    transform: translateY(-100%) translateX(500%);
+  }
+}
+
+@-moz-keyframes ball-running-dots-animate {
+
+  0%,
+  100% {
+    width: 100%;
+    height: 100%;
+    -moz-transform: translateY(0) translateX(500%);
+    transform: translateY(0) translateX(500%);
+  }
+
+  80% {
+    -moz-transform: translateY(0) translateX(0);
+    transform: translateY(0) translateX(0);
+  }
+
+  85% {
+    width: 100%;
+    height: 100%;
+    -moz-transform: translateY(-125%) translateX(0);
+    transform: translateY(-125%) translateX(0);
+  }
+
+  90% {
+    width: 200%;
+    height: 75%;
+  }
+
+  95% {
+    width: 100%;
+    height: 100%;
+    -moz-transform: translateY(-100%) translateX(500%);
+    transform: translateY(-100%) translateX(500%);
+  }
+}
+
+@-o-keyframes ball-running-dots-animate {
+
+  0%,
+  100% {
+    width: 100%;
+    height: 100%;
+    -o-transform: translateY(0) translateX(500%);
+    transform: translateY(0) translateX(500%);
+  }
+
+  80% {
+    -o-transform: translateY(0) translateX(0);
+    transform: translateY(0) translateX(0);
+  }
+
+  85% {
+    width: 100%;
+    height: 100%;
+    -o-transform: translateY(-125%) translateX(0);
+    transform: translateY(-125%) translateX(0);
+  }
+
+  90% {
+    width: 200%;
+    height: 75%;
+  }
+
+  95% {
+    width: 100%;
+    height: 100%;
+    -o-transform: translateY(-100%) translateX(500%);
+    transform: translateY(-100%) translateX(500%);
+  }
+}
+
+@keyframes ball-running-dots-animate {
+
+  0%,
+  100% {
+    width: 100%;
+    height: 100%;
+    -webkit-transform: translateY(0) translateX(500%);
+    -moz-transform: translateY(0) translateX(500%);
+    -o-transform: translateY(0) translateX(500%);
+    transform: translateY(0) translateX(500%);
+  }
+
+  80% {
+    -webkit-transform: translateY(0) translateX(0);
+    -moz-transform: translateY(0) translateX(0);
+    -o-transform: translateY(0) translateX(0);
+    transform: translateY(0) translateX(0);
+  }
+
+  85% {
+    width: 100%;
+    height: 100%;
+    -webkit-transform: translateY(-125%) translateX(0);
+    -moz-transform: translateY(-125%) translateX(0);
+    -o-transform: translateY(-125%) translateX(0);
+    transform: translateY(-125%) translateX(0);
+  }
+
+  90% {
+    width: 200%;
+    height: 75%;
+  }
+
+  95% {
+    width: 100%;
+    height: 100%;
+    -webkit-transform: translateY(-100%) translateX(500%);
+    -moz-transform: translateY(-100%) translateX(500%);
+    -o-transform: translateY(-100%) translateX(500%);
+    transform: translateY(-100%) translateX(500%);
+  }
 }

+ 216 - 306
src/components/chat/ChatInput.vue

@@ -1,10 +1,23 @@
 <script setup>
-import { unref } from 'vue';
+import { ref, unref, onMounted } from 'vue';
 
 const modelInpValue = defineModel();
 const emit = defineEmits(['on-submit']);
 const modelLoading = defineModel('loading');
 
+const isRecording = ref(false);
+const recordingTip = ref('松开 发送');
+const showVolume = ref(false);
+const volumeLevel = ref(50); // 模拟音量级别
+let recorderManager = null;
+const audioPath = ref(null);
+
+
+const innerAudioContext = wx.createInnerAudioContext();
+
+// 类型 input|voice
+const inpType = ref('input'); 
+
 // 提交问题
 const onSubmit = () => {
   const val = unref(modelInpValue);
@@ -25,27 +38,152 @@ const onSubmit = () => {
 
   emit('on-submit', { showVal: val, question: val });
 };
+
+const onChangeInpType = () => {
+  inpType.value = inpType.value === 'input' ? 'voice' : 'input';
+}
+
+
+const playRecording = () => {
+  
+  if (innerAudioContext) {
+    innerAudioContext.src = audioPath.value;
+    innerAudioContext.play();
+    
+    innerAudioContext.onPlay(() => {
+      console.log('开始播放');
+    });
+    
+    innerAudioContext.onError((err) => {
+      console.error('播放错误:', err);
+      uni.showToast({ title: '播放失败', icon: 'none' });
+    });
+  }
+}
+
+// 语音播放相关
+const startRecording = e => {
+  isRecording.value = true;
+  showVolume.value = true;
+  
+  recorderManager.start({
+    format: 'mp3',
+    duration: 60000, // 最长1分钟
+    sampleRate: 44100,
+    numberOfChannels: 1,
+    encodeBitRate: 192000,
+    frameSize: 50, // 帧大小,影响onFrameRecorded回调频率
+  });
+}
+
+function setupRecorderEvents() {
+  if (!recorderManager) return;
+  
+  recorderManager.onStart(() => {
+    console.log('录音开始');
+  });
+  
+  recorderManager.onPause(() => {
+    console.log('录音暂停');
+  });
+  
+  recorderManager.onStop((res) => {
+    // audioPath.value = 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3';
+
+    audioPath.value = res.tempFilePath;
+
+    // setTimeout(() => {
+    //   innerAudioContext.src = 'https://dlink.host/musics/aHR0cHM6Ly9vbmVkcnYtbXkuc2hhcmVwb2ludC5jb20vOnU6L2cvcGVyc29uYWwvc3Rvcl9vbmVkcnZfb25taWNyb3NvZnRfY29tL0VjYzBzQUxiWFk5TWdHQl9GUVNkV2pJQm5wRmM0MktDZWpURnhhMjhELUdXeVE.mp3';
+    //   innerAudioContext.play();
+    // }, 1000)
+
+    console.log('录音停止', res);
+    // 这里可以处理录音结果,如上传或播放
+  });
+  recorderManager.onFrameRecorded((res) => {
+    // 可以在这里获取实时音量等信息
+    const volume = res.volumn; // 注意微信可能是volumn而不是volume
+    if (volume !== undefined) {
+      volumeLevel.value = Math.min(100, volume * 200); // 调整音量显示比例
+    }
+  });
+}
+
+function stopRecording() {
+  if (!isRecording.value) return;
+  
+  isRecording.value = false;
+  showVolume.value = false;
+  
+  recorderManager.stop();
+  
+  // 停止音量检测
+  // stopVolumeDetection();
+}
+
+// 初始化录音管理器
+onMounted(() => {
+  recorderManager = wx.getRecorderManager();
+
+  setupRecorderEvents();
+});
 </script>
 
 <template>
+  <view class="voice-wrapper" v-show="isRecording">
+    <view class="voice-inner">
+      <view class="voice-card">
+        <view class="voice-tip">
+        </view>
+      </view>
+      <view
+        class="vocie-btn_wrapper"
+        @touchstart="startRecording"
+        @touchend="stopRecording"
+        @touchcancel="stopRecording"
+      >
+        <view class="tips">{{ recordingTip }}</view>
+        <view class="btn">
+          <uni-icons type="sound" size="20" color="#777"></uni-icons>
+        </view>
+      </view>
+    </view>
+  </view>
+
   <view class="chat-inp-container">
+    <!-- <view @click="playRecording">按钮</view> -->
     <view class="chat-inp-inner">
-      <view class="voice-btn">
+      <view class="voice-btn" @click="onChangeInpType">
         <TheSvgIcon class="icon" src="icon-voice" size="42"></TheSvgIcon>
       </view>
       <view class="inp-inner">
-        <textarea v-model.trim="modelInpValue" auto-height :maxlength="2000" class="chat-inp" placeholder="输入您的问题或需求"
-          placeholder-style="color:#9A9A9A">
+        <textarea
+          v-model.trim="modelInpValue"
+          :show-confirm-bar="false"
+          :cursor-spacing="30"
+          auto-height
+          :maxlength="2000"
+          class="chat-inp"
+          placeholder="输入您的问题或需求"
+          placeholder-style="color:#9A9A9A"
+          v-show="inpType == 'input'"
+        >
         </textarea>
+
+        <view
+          class="chat-voice"
+          v-show="inpType == 'voice'"
+          @touchstart="startRecording"
+          @touchend="stopRecording"
+          @touchcancel="stopRecording"
+        >
+          <text>按住 说话</text>
+        </view>
       </view>
       <view class="send-btn" @click="onSubmit">
         <TheSvgIcon class="icon" src="icon-send-plane" size="30" v-show="!modelLoading"></TheSvgIcon>
         <view class="la-ball-running-dots la-sm" v-show="modelLoading">
-          <view class="item"></view>
-          <view class="item"></view>
-          <view class="item"></view>
-          <view class="item"></view>
-          <view class="item"></view>
+          <view class="item" v-for="item in 5" :key="item"></view>
         </view>
       </view>
     </view>
@@ -59,8 +197,8 @@ const onSubmit = () => {
 
   .chat-inp-inner {
     display: flex;
-    padding: 26rpx 28rpx;
     align-items: flex-end;
+    padding: 26rpx 28rpx;
     border-radius: 32rpx;
     background: #FFF;
     box-shadow: 0px 8rpx 16rpx 2rpx rgba(172, 200, 224, 0.20);
@@ -68,26 +206,32 @@ const onSubmit = () => {
     .voice-btn {
       display: flex;
       align-items: center;
-      justify-content: center;
+      justify-content: flex-end;
       height: 56rpx;
     }
 
     .inp-inner {
       width: 100%;
-      height: 100%;
-      margin-bottom: 8rpx;
+      min-height: 56rpx;
+      display: flex;
+      align-items: center;
+      // padding-bottom: 10rpx;
 
       .chat-inp {
         width: 100%;
-        height: 100%;
         max-height: 200rpx;
-        width: 100%;
-        height: 32rpx;
+        height: 100%;
         padding: 0 16rpx;
         font-size: 28rpx;
         color: #333;
         box-sizing: border-box;
       }
+
+      .chat-voice {
+        width: 100%;
+        font-size: 28rpx;
+        text-align: center;
+      }
     }
 
     .send-btn {
@@ -106,299 +250,65 @@ const onSubmit = () => {
   }
 }
 
-.la-ball-running-dots,
-.la-ball-running-dots>.item {
-  position: relative;
-  -webkit-box-sizing: border-box;
-  -moz-box-sizing: border-box;
-  box-sizing: border-box;
-}
-
-.la-ball-running-dots {
-  // display: block;
-  font-size: 0;
-  color: #fff;
-}
-
-.la-ball-running-dots.la-dark {
-  color: #333;
-}
-
-.la-ball-running-dots>.item {
-  display: inline-block;
-  float: none;
-  background-color: currentColor;
-  border: 0 solid currentColor;
-}
-
-.la-ball-running-dots {
-  width: 10px;
-  height: 10px;
-}
-
-.la-ball-running-dots>.item {
+.voice-wrapper {
   position: absolute;
-  width: 20rpx;
-  height: 20rpx;
-  margin-left: -25px;
-  border-radius: 100%;
-  -webkit-animation: ball-running-dots-animate 2s linear infinite;
-  -moz-animation: ball-running-dots-animate 2s linear infinite;
-  -o-animation: ball-running-dots-animate 2s linear infinite;
-  animation: ball-running-dots-animate 2s linear infinite;
-}
-
-.la-ball-running-dots>.item:nth-child(1) {
-  -webkit-animation-delay: 0s;
-  -moz-animation-delay: 0s;
-  -o-animation-delay: 0s;
-  animation-delay: 0s;
-}
-
-.la-ball-running-dots>.item:nth-child(2) {
-  -webkit-animation-delay: -.4s;
-  -moz-animation-delay: -.4s;
-  -o-animation-delay: -.4s;
-  animation-delay: -.4s;
-}
-
-.la-ball-running-dots>.item:nth-child(3) {
-  -webkit-animation-delay: -.8s;
-  -moz-animation-delay: -.8s;
-  -o-animation-delay: -.8s;
-  animation-delay: -.8s;
-}
-
-.la-ball-running-dots>.item:nth-child(4) {
-  -webkit-animation-delay: -1.2s;
-  -moz-animation-delay: -1.2s;
-  -o-animation-delay: -1.2s;
-  animation-delay: -1.2s;
-}
-
-.la-ball-running-dots>.item:nth-child(5) {
-  -webkit-animation-delay: -1.6s;
-  -moz-animation-delay: -1.6s;
-  -o-animation-delay: -1.6s;
-  animation-delay: -1.6s;
-}
-
-.la-ball-running-dots>.item:nth-child(6) {
-  -webkit-animation-delay: -2s;
-  -moz-animation-delay: -2s;
-  -o-animation-delay: -2s;
-  animation-delay: -2s;
-}
-
-.la-ball-running-dots>.item:nth-child(7) {
-  -webkit-animation-delay: -2.4s;
-  -moz-animation-delay: -2.4s;
-  -o-animation-delay: -2.4s;
-  animation-delay: -2.4s;
-}
-
-.la-ball-running-dots>.item:nth-child(8) {
-  -webkit-animation-delay: -2.8s;
-  -moz-animation-delay: -2.8s;
-  -o-animation-delay: -2.8s;
-  animation-delay: -2.8s;
-}
-
-.la-ball-running-dots>.item:nth-child(9) {
-  -webkit-animation-delay: -3.2s;
-  -moz-animation-delay: -3.2s;
-  -o-animation-delay: -3.2s;
-  animation-delay: -3.2s;
-}
-
-.la-ball-running-dots>.item:nth-child(10) {
-  -webkit-animation-delay: -3.6s;
-  -moz-animation-delay: -3.6s;
-  -o-animation-delay: -3.6s;
-  animation-delay: -3.6s;
-}
-
-.la-ball-running-dots.la-sm {
-  width: 8rpx;
-  height: 8rpx;
-}
-
-.la-ball-running-dots.la-sm>.item {
-  width: 8rpx;
-  height: 8rpx;
-  margin-left: -12px;
-}
-
-.la-ball-running-dots.la-2x {
-  width: 20px;
-  height: 20px;
-}
-
-.la-ball-running-dots.la-2x>.item {
-  width: 20px;
-  height: 20px;
-  margin-left: -50px;
-}
-
-.la-ball-running-dots.la-3x {
-  width: 30px;
-  height: 30px;
-}
-
-.la-ball-running-dots.la-3x>.item {
-  width: 30px;
-  height: 30px;
-  margin-left: -75px;
-}
-
-/*
- * Animation
- */
-@-webkit-keyframes ball-running-dots-animate {
-
-  0%,
-  100% {
-    width: 100%;
-    height: 100%;
-    -webkit-transform: translateY(0) translateX(500%);
-    transform: translateY(0) translateX(500%);
-  }
-
-  80% {
-    -webkit-transform: translateY(0) translateX(0);
-    transform: translateY(0) translateX(0);
-  }
-
-  85% {
-    width: 100%;
-    height: 100%;
-    -webkit-transform: translateY(-125%) translateX(0);
-    transform: translateY(-125%) translateX(0);
-  }
-
-  90% {
-    width: 200%;
-    height: 75%;
-  }
-
-  95% {
-    width: 100%;
-    height: 100%;
-    -webkit-transform: translateY(-100%) translateX(500%);
-    transform: translateY(-100%) translateX(500%);
-  }
-}
-
-@-moz-keyframes ball-running-dots-animate {
-
-  0%,
-  100% {
-    width: 100%;
-    height: 100%;
-    -moz-transform: translateY(0) translateX(500%);
-    transform: translateY(0) translateX(500%);
-  }
-
-  80% {
-    -moz-transform: translateY(0) translateX(0);
-    transform: translateY(0) translateX(0);
-  }
-
-  85% {
-    width: 100%;
-    height: 100%;
-    -moz-transform: translateY(-125%) translateX(0);
-    transform: translateY(-125%) translateX(0);
-  }
-
-  90% {
-    width: 200%;
-    height: 75%;
-  }
-
-  95% {
-    width: 100%;
-    height: 100%;
-    -moz-transform: translateY(-100%) translateX(500%);
-    transform: translateY(-100%) translateX(500%);
-  }
-}
-
-@-o-keyframes ball-running-dots-animate {
-
-  0%,
-  100% {
-    width: 100%;
-    height: 100%;
-    -o-transform: translateY(0) translateX(500%);
-    transform: translateY(0) translateX(500%);
-  }
-
-  80% {
-    -o-transform: translateY(0) translateX(0);
-    transform: translateY(0) translateX(0);
-  }
-
-  85% {
-    width: 100%;
-    height: 100%;
-    -o-transform: translateY(-125%) translateX(0);
-    transform: translateY(-125%) translateX(0);
-  }
-
-  90% {
-    width: 200%;
-    height: 75%;
-  }
-
-  95% {
-    width: 100%;
-    height: 100%;
-    -o-transform: translateY(-100%) translateX(500%);
-    transform: translateY(-100%) translateX(500%);
-  }
-}
-
-@keyframes ball-running-dots-animate {
-
-  0%,
-  100% {
-    width: 100%;
-    height: 100%;
-    -webkit-transform: translateY(0) translateX(500%);
-    -moz-transform: translateY(0) translateX(500%);
-    -o-transform: translateY(0) translateX(500%);
-    transform: translateY(0) translateX(500%);
-  }
-
-  80% {
-    -webkit-transform: translateY(0) translateX(0);
-    -moz-transform: translateY(0) translateX(0);
-    -o-transform: translateY(0) translateX(0);
-    transform: translateY(0) translateX(0);
-  }
-
-  85% {
-    width: 100%;
+  top: 0;
+  left: 0;
+  width: 100vh;
+  height: 100vh;
+  background: rgba(0, 0, 0, 0.7);
+  z-index: 10;
+
+  .voice-inner {
+    display: flex;
+    justify-content: space-between;
+    flex-flow: column;
     height: 100%;
-    -webkit-transform: translateY(-125%) translateX(0);
-    -moz-transform: translateY(-125%) translateX(0);
-    -o-transform: translateY(-125%) translateX(0);
-    transform: translateY(-125%) translateX(0);
-  }
 
-  90% {
-    width: 200%;
-    height: 75%;
-  }
-
-  95% {
-    width: 100%;
-    height: 100%;
-    -webkit-transform: translateY(-100%) translateX(500%);
-    -moz-transform: translateY(-100%) translateX(500%);
-    -o-transform: translateY(-100%) translateX(500%);
-    transform: translateY(-100%) translateX(500%);
+    .voice-card {
+      flex:1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100vw;
+      .voice-tip {
+        width: 100px;
+        height: 30px;
+        background: red;
+      }
+    }
+  
+    .vocie-btn_wrapper {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      flex-flow: column;
+      flex-shrink: 0;
+      height: 200rpx;
+      width: 100vw;
+      overflow: hidden;
+      .tips {
+        padding-bottom: 10rpx;
+        flex-shrink: 0;
+        color: #fff;
+        font-size: 24rpx;
+        text-align: center;
+      }
+      .btn {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100vw;
+        height: 120rpx;
+        border-top-left-radius: 50%;
+        border-top-right-radius: 50%;
+        border-top: 1px solid #fff;
+        background: #b5b5b5;
+        padding-bottom: env(safe-area-inset-bottom);
+      }
+    }
   }
 }
 </style>

+ 6 - 1
src/components/chat/ChatWelcome.vue

@@ -37,7 +37,8 @@ const handleEmit = ({ content: realQuestion, question }) => {
       <view class="question-card">
         <view class="title-wrap">
           <image src="https://static.fuxicarbon.com/bigModel/wechat/layout/icon-start.svg" alt=""  class="icon-start"/>
-          <text class="title">{{ cardTitle }}</text>
+          <image src="https://static.fuxicarbon.com/bigModel/wechat/layout/icon-welcome.svg" alt=""  class="icon-welcome"/>
+          <!-- <text class="title">{{ cardTitle }}</text> -->
         </view>
         <view class="list">
           <view class="item" v-for="item in cardContent" :key="item.id" @click="handleEmit(item)">
@@ -96,6 +97,10 @@ const handleEmit = ({ content: realQuestion, question }) => {
           height: 40rpx;
           margin-right: 8rpx;
         }
+        .icon-welcome {
+          width: 224rpx;
+          height: 40rpx;
+        }
       } 
 
       .title {

+ 4 - 4
src/pages/analyse/water/details.vue

@@ -262,10 +262,6 @@ onLoad(async ({ id, title }) => {
           font-weight: 500;
           line-height: 48rpx;
         }
-        .is-exceed {
-          color: #F44C49;
-          font-weight: bold;
-        }
         :deep(.uni-table-th) {
           padding: 14rpx 20rpx;
           background: #E6E9F2;
@@ -279,6 +275,10 @@ onLoad(async ({ id, title }) => {
           font-size: 28rpx;
           color: #212121;
         }
+        .is-exceed {
+          color: #F44C49 ;
+          font-weight: bold;
+        }
       }
     }
   }

+ 50 - 5
src/pages/analyse/water/index.vue

@@ -6,6 +6,10 @@ import RecodeItem from "@/components/RecodeItem"
 
 const pagingRef = ref(null);
 const recordList = ref([]);
+const tabActiveIndex = ref(0);
+
+const tabData = ['正在报警', '历史报警'];
+
 
 // 点击报警列表
 const handleOpenContent = ({ id, category, reason: title, }) => {
@@ -16,19 +20,25 @@ const handleOpenContent = ({ id, category, reason: title, }) => {
 
 // 查询数据
 const queryList = (pageNum, pageSize) => {
-  waterApi.getWaterWarningList({ type: 0, warningStatus: 0, pageNum, pageSize }).then(({ rows }) => {
+  waterApi.getWaterWarningList({ type: 0, warningStatus: tabActiveIndex.value, pageNum, pageSize }).then(({ rows }) => {
     pagingRef.value.complete(rows);
   })
 }
+
+const onSwitchWarningStatus = (index) => {
+  tabActiveIndex.value = index;
+  pagingRef.value.reload();
+}
 </script>
 
 <template>
   <z-paging ref="pagingRef" bg-color="linear-gradient(240deg, #dce8fd 0%, #f6fbfe 100%)" v-model="recordList"
     @query="queryList" refresher-enabled>
     <template #top>
-      <BaseNavBar titleText="水质报警" isDropdown>
-        <view></view>
-      </BaseNavBar>
+      <BaseNavBar titleText="水质报警" isDropdown><view></view></BaseNavBar>
+      <view class="tabs">
+        <view :class="['tab', { active: tabActiveIndex === index }]" @click="onSwitchWarningStatus(index)" v-for="item, index in tabData" :key="item">{{ item }}</view>
+      </view>
     </template>
     <view class="warning-list">
       <RecodeItem :item="item" v-for="item in recordList" :key="item.id" @on-click="handleOpenContent"></RecodeItem>
@@ -40,8 +50,43 @@ const queryList = (pageNum, pageSize) => {
 </template>
 
 <style lang="scss" scoped>
+.tabs {
+  width: 100vw;
+  padding: 32rpx 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  .tab {
+    color: #7B909C;
+    padding-bottom: 10rpx;
+    font-size: 32rpx;
+    font-style: normal;
+    line-height: 40rpx;
+    &:nth-child(1) {
+      margin-right: 22rpx;
+    }
+  }
+  .tab.active {
+    position: relative;
+    color: #212121;
+    font-weight: 500;
+    &:after {
+      content: '';
+      display: block;
+      position: absolute;
+      left: 50%;
+      bottom: 0px;
+      transform: translateX(-50%);
+      width: 48rpx;
+      height: 6rpx;
+      border-radius: 4rpx;
+      background: #2454FF;
+    }
+  }
+}
 .warning-list {
-  padding: 60rpx 40rpx 40rpx 40rpx;
+  padding: 0rpx 40rpx 40rpx 40rpx;
   :deep(.warning-item) {
     margin-bottom: 32rpx;
   }

+ 12 - 0
src/utils/format.js

@@ -89,4 +89,16 @@ export const getFormatYesterDay = ( data ) => {
 // 格式化小数
 export const formatDecimals = ( num, digits = 2 ) => {
   return !(typeof num !== 'number' || isNaN(num)) ? Number(Number(num).toFixed(digits)) : ''
+}
+
+export const uint8ArrayToString = (uint8Array) => {
+  let str = '';
+  for (let i = 0; i < uint8Array.length; i++) {
+    str += String.fromCharCode(uint8Array[i]);
+  }
+  try {
+    return decodeURIComponent(escape(str));
+  } catch (e) {
+    return str;
+  }
 }

+ 6 - 6
src/utils/https.js

@@ -20,17 +20,17 @@ const httpInterceptor = {
 uni.addInterceptor('request', httpInterceptor);
 
 export const http = (options) => {
-  uni.showLoading({
-    title: "加载中...",
-    mask: true
-  })
+  // uni.showLoading({
+  //   title: "加载中...",
+  //   mask: true
+  // })
   return new Promise((resolve, reject) => {
     uni.request({
       ...options,
       customUrl: options.url,
       success(result) {
         const { data: res } = result;
-        uni.hideLoading();
+        // uni.hideLoading();
         
         switch(res.code){
           case 200:
@@ -53,7 +53,7 @@ export const http = (options) => {
         }
       },
       fail(err) {
-        uni.hideLoading();
+        // uni.hideLoading();
         uni.showToast({
           icon: 'none',
           title: '网络错误,换个网络试试',

+ 7 - 6
src/utils/streamRequest.js

@@ -1,14 +1,10 @@
 import { useUserStore } from "@/stores/modules/userStore";
+import { uint8ArrayToString } from "@/utils/format";
 
 const userStore = useUserStore();
 const token = userStore.userInfo?.token;
 const baseURL = import.meta.env.VITE_APP_BASE_API;
 
-const decoder = new TextDecoder("utf-8");
-
-// 解码 UTF-8 字符串
-const decodeUTF8 = arrBuff => decoder.decode(new Uint8Array(arrBuff));
-
 // stream request - chat
 export const streamChatRequest = async ({ data, onProgress, onSuccess, onComplete, onError, onAbort } ) => {
 
@@ -21,6 +17,7 @@ export const streamChatRequest = async ({ data, onProgress, onSuccess, onComplet
 
   const requestTask = uni.request({
     url,
+    timeout: 20 * 60 * 1000,
     enableChunked: true,
     method: "POST",
     header: {
@@ -29,12 +26,16 @@ export const streamChatRequest = async ({ data, onProgress, onSuccess, onComplet
     },
     data,
     success: () => {
+      console.log("success");
       onSuccess && onSuccess(accumulatedText);
     },
     fail: (err) => {
+      console.log("err", err);
       onError && onError(err);
     },
     complete: () => {
+
+    console.log("complete");
       onComplete && onComplete();
     }
   });
@@ -43,7 +44,7 @@ export const streamChatRequest = async ({ data, onProgress, onSuccess, onComplet
 
   requestTask.onChunkReceived(async (res) => {
     const uint8Array = new Uint8Array(res.data);
-    const chunkText = decodeUTF8([...uint8Array]);
+    const chunkText = uint8ArrayToString(uint8Array);;
     accumulatedText += chunkText;
     onProgress && onProgress(accumulatedText);
   });