Browse Source

Merge branch 'master' of https://git.nanodreamtech.com/wkw/wallet_app

wkw 1 week ago
parent
commit
9b4dc6f281
3 changed files with 118 additions and 145 deletions
  1. 0 84
      src/directives/longpress.js
  2. 2 4
      src/main.js
  3. 116 57
      src/views/im/chat/index.vue

+ 0 - 84
src/directives/longpress.js

@@ -1,84 +0,0 @@
-export default {
-  mounted(el, binding) {
-    let pressTimer = null;
-    let hasLongPressed = false;
-    const duration = binding.arg || 1000; // 默认1秒
-
-    // 开始按压
-    const start = (e) => {
-      // 防止重复触发
-      if (pressTimer !== null) return;
-
-      // 重置状态
-      hasLongPressed = false;
-
-      // 设置定时器
-      pressTimer = setTimeout(() => {
-        hasLongPressed = true;
-        // 触发长按回调
-        if (typeof binding.value === "function") {
-          binding.value(e, "longpress");
-        } else if (binding.value?.longpress) {
-          binding.value.longpress(e);
-        }
-      }, duration);
-    };
-
-    // 结束按压
-    const end = (e) => {
-      // 清除定时器
-      if (pressTimer) {
-        clearTimeout(pressTimer);
-        pressTimer = null;
-      }
-
-      // 如果已经触发了长按,则执行release回调
-      if (hasLongPressed) {
-        if (binding.value?.release) {
-          binding.value.release(e);
-        }
-      }
-      // 否则执行pressEnd回调(未达到长按时间就放开)
-      else {
-        if (binding.value?.pressEnd) {
-          binding.value.pressEnd(e);
-        }
-      }
-
-      hasLongPressed = false;
-    };
-
-    // 取消按压(鼠标移出元素)
-    const cancel = (e) => {
-      if (pressTimer) {
-        clearTimeout(pressTimer);
-        pressTimer = null;
-        hasLongPressed = false;
-      }
-    };
-
-    // 添加事件监听
-    el.addEventListener("mousedown", start);
-    el.addEventListener("touchstart", start);
-    el.addEventListener("click", end);
-    el.addEventListener("mouseup", end);
-    el.addEventListener("mouseleave", cancel);
-    el.addEventListener("touchend", end);
-    el.addEventListener("touchcancel", cancel);
-
-    // 保存以便卸载时移除
-    el._longPressHandlers = { start, end, cancel };
-  },
-
-  unmounted(el) {
-    // 移除事件监听
-    const { start, end, cancel } = el._longPressHandlers || {};
-    if (start) el.removeEventListener("mousedown", start);
-    if (start) el.removeEventListener("touchstart", start);
-    if (end) el.removeEventListener("click", end);
-    if (end) el.removeEventListener("mouseup", end);
-    if (cancel) el.removeEventListener("mouseleave", cancel);
-    if (end) el.removeEventListener("touchend", end);
-    if (cancel) el.removeEventListener("touchcancel", cancel);
-  },
-};

+ 2 - 4
src/main.js

@@ -19,8 +19,7 @@ import SvgIcon from "@/components/Svg-icon/SvgIcon.vue";
 // windows 挂载 & 注入
 import { initCapacitor } from './plugins';
 import { setup  } from './plugins/storage'; 
-
-import longpress from '@/directives/longpress';
+ 
 
 import VConsole from 'vconsole';
 new VConsole(); 
@@ -34,8 +33,7 @@ async function appInit() {
   // UI
   app.use(Vant);
   app.use(Lazyload);
-  app.use(i18n); 
-  app.directive('longpress', longpress);
+  app.use(i18n);  
   // windows 挂载
   setup()
 

+ 116 - 57
src/views/im/chat/index.vue

@@ -1,5 +1,8 @@
 <template>
-  <div class="container" :style="{ height: `calc(100% - ${currentBottomHeight}px)` }">
+  <div
+    class="container"
+    :style="{ height: `calc(100% - ${currentBottomHeight}px)` }"
+  >
     <div class="chat-bg"></div>
     <!-- 顶部导航 -->
     <div class="header-chat">
@@ -11,9 +14,16 @@
     <!-- 聊天消息区域 -->
     <div class="chat-list" ref="chatListRef">
       <div v-for="(item, index) in wsStore.messages" :key="index">
-        <div class="chat-time">{{ formatTime(item.timestamp || Date.now()) }}</div>
+        <div class="chat-time">
+          {{ formatTime(item.timestamp || Date.now()) }}
+        </div>
         <div class="box">
-          <div class="list-item" :class="walletStore.account == item.toUsername ?'' : 'flex-reverse'">
+          <div
+            class="list-item"
+            :class="
+              walletStore.account == item.toUsername ? '' : 'flex-reverse'
+            "
+          >
             <!-- 头像 -->
             <van-image
               class="list-img"
@@ -24,8 +34,10 @@
             />
             <!-- 内容 -->
             <div class="list-cont">
-              <div>{{ item.fromUsername || '匿名用户' }}</div>
-              <div class="content" v-if="item.contentType === 1">{{ item.content }}</div>
+              <div>{{ item.fromUsername || "匿名用户" }}</div>
+              <div class="content" v-if="item.contentType === 1">
+                {{ item.content }}
+              </div>
               <!-- 可扩展 contentType === 2 图片,3 名片等 -->
             </div>
           </div>
@@ -36,7 +48,15 @@
     <!-- 输入框 -->
     <div class="page-foot">
       <div class="flex-box">
-        <svg-icon class="page-icon" name="voice" v-longpress="pressEvents" />
+        <svg-icon
+          type="button"
+          class="page-icon"
+          name="voice"
+          @mousedown="startAudio"
+          @touchstart="startAudio"
+          @mouseup="sendAudioMessage"
+          @touchend="sendAudioMessage"
+        />
         <van-field
           rows="1"
           type="textarea"
@@ -48,32 +68,62 @@
           placeholder="输入文本"
           @keyup.enter="sendMessage"
         />
-        <svg-icon class="page-icon mr12 emoji-toggle" name="emoji" @click="toggleAppBox(1)" />
-        <svg-icon class="page-icon tools-toggle" name="add2" @click="toggleAppBox(2)" />
+        <svg-icon
+          class="page-icon mr12 emoji-toggle"
+          name="emoji"
+          @click="toggleAppBox(1)"
+        />
+        <svg-icon
+          class="page-icon tools-toggle"
+          name="add2"
+          @click="toggleAppBox(2)"
+        />
       </div>
 
       <!-- 表情面板 -->
-      <div class="app-box" v-show="showEmoji" :style="{ height: `${appBoxHeight}px` }" ref="emojiRef">
+      <div
+        class="app-box"
+        v-show="showEmoji"
+        :style="{ height: `${appBoxHeight}px` }"
+        ref="emojiRef"
+      >
         😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍
       </div>
 
       <!-- 工具栏面板 -->
-      <div class="app-box" v-show="showTools" :style="{ height: `${appBoxHeight}px` }" ref="toolsRef">
+      <div
+        class="app-box"
+        v-show="showTools"
+        :style="{ height: `${appBoxHeight}px` }"
+        ref="toolsRef"
+      >
         <div class="tool-btn">
           <svg-icon class="tool-icon" name="tp" />
           <div>图片</div>
         </div>
-        <div class="tool-btn"><svg-icon class="tool-icon" name="ps" /><div>拍摄</div></div>
-        <div class="tool-btn"><svg-icon class="tool-icon" name="yyth" /><div>语音通话</div></div>
-        <div class="tool-btn"><svg-icon class="tool-icon" name="spth" /><div>视频通话</div></div>
-        <div class="tool-btn"><svg-icon class="tool-icon" name="mp" /><div>名片</div></div>
+        <div class="tool-btn">
+          <svg-icon class="tool-icon" name="ps" />
+          <div>拍摄</div>
+        </div>
+        <div class="tool-btn">
+          <svg-icon class="tool-icon" name="yyth" />
+          <div>语音通话</div>
+        </div>
+        <div class="tool-btn">
+          <svg-icon class="tool-icon" name="spth" />
+          <div>视频通话</div>
+        </div>
+        <div class="tool-btn">
+          <svg-icon class="tool-icon" name="mp" />
+          <div>名片</div>
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted, onUnmounted,onBeforeUnmount } from "vue";
+import { ref, computed, onMounted, onUnmounted, onBeforeUnmount } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { useWebSocketStore } from "@/stores/modules/webSocketStore.js";
 import { useWalletStore } from "@/stores/modules/walletStore.js";
@@ -114,8 +164,9 @@ const isMobile = Capacitor.getPlatform() !== "web";
 
 // 底部高度动态计算
 // 语音
-const mediaRecorder = ref(null);  // 录音对象
-const audioChunks = ref([]) // 录音数据
+const isTouchDevice = ref(false);
+const mediaRecorder = ref(null); // 录音对象
+const audioChunks = ref([]); // 录音数据
 
 // 计算当前底部总高度
 const currentBottomHeight = computed(() => {
@@ -158,25 +209,35 @@ const setupKeyboardListeners = async () => {
 };
 
 // 录音
-const startAudio = async () => {
-    try {
+const startAudio = async (event) => {
+  if (event.type === "touchstart") {
+    isTouchDevice.value = true;
+  }
+
+  // 如果是触摸设备且事件是鼠标事件,则忽略
+  if (isTouchDevice.value && event.type === "mousedown") {
+    return;
+  }
+  try {
     // 请求麦克风权限
     const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
 
     // 创建 MediaRecorder 实例
-    mediaRecorder.value = new MediaRecorder(stream, { mimeType: 'audio/webm; codecs=opus' });
-    
+    mediaRecorder.value = new MediaRecorder(stream, {
+      mimeType: "audio/webm; codecs=opus",
+    });
+
     // 收集音频数据
     mediaRecorder.value.ondataavailable = (e) => {
       audioChunks.value.push(e.data);
     };
-    
+
     mediaRecorder.value.start(1000); // 每1秒收集一次数据
-    console.log('Recording started');
+    console.log("Recording started");
   } catch (error) {
-    console.error('Error accessing microphone:', error);
+    console.error("Error accessing microphone:", error);
   }
-}
+};
 // 停止录音
 const stopRecording = async () => {
   return new Promise(async (resolve) => {
@@ -184,67 +245,63 @@ const stopRecording = async () => {
       resolve(new Uint8Array());
       return;
     }
-    
+
     // 停止录音
     mediaRecorder.value.stop();
-    mediaRecorder.value.stream.getTracks().forEach(track => track.stop());
-    
+    mediaRecorder.value.stream.getTracks().forEach((track) => track.stop());
+
     // 等待最后的数据可用
     mediaRecorder.value.onstop = async () => {
       // 合并所有音频片段
-      const audioBlob = new Blob(audioChunks.value, { type: 'audio/webm' });
-      
+      const audioBlob = new Blob(audioChunks.value, { type: "audio/webm" });
+
       // 转换为 Uint8Array
       const arrayBuffer = await audioBlob.arrayBuffer();
       const audioData = new Uint8Array(arrayBuffer);
-      
+
       resolve(audioData);
     };
   });
-}
+};
 
-const sendAudioMessage = async () => {
+const sendAudioMessage = async (event) => {
+  if (isTouchDevice.value && event.type === "mouseup") {
+    return;
+  }
+  console.log("发送音频消息");
   try {
     // 1. 停止录音并获取音频数据
     const audioData = await stopRecording();
-    
+
     // 2. 准备消息体
     const message = {
       content: text.value, // 如果有文本内容
-      contentType: MSG_TYPE.AUDIO,  // 音频消息类型
+      contentType: MSG_TYPE.AUDIO, // 音频消息类型
       messageType: MESSAGE_TYPE_USER, // 单聊消息
       to: route.query.uuid, // 接收方ID
       fileSuffix: "webm", // 使用webm后缀更准确
-      file: Array.from(audioData) // 将Uint8Array转为普通数组
+      file: Array.from(audioData), // 将Uint8Array转为普通数组
     };
-    
+
     // 3. 通过WebSocket发送
     wsStore.sendMessage(message);
-    
+
     // 4. 重置状态
     mediaRecorder.value = null;
     audioChunks.value = [];
-    
   } catch (error) {
-    console.error('Error sending audio message:', error);
-  }
-}
-
-const pressEvents = (event, type) => {
-  if (type === 'longpress') {
-    console.log('长按触发');
-  } else {
-    console.log('点击/放开触发');
+    console.error("Error sending audio message:", error);
   }
 };
+
 // 发送消息
 const sendMessage = () => {
   if (!text.value.trim()) return;
   const message = {
     content: text.value,
-    contentType: MSG_TYPE.TEXT,  // 1: 文本消息 
+    contentType: MSG_TYPE.TEXT, // 1: 文本消息
     messageType: MESSAGE_TYPE_USER, // 1: 单聊天
-    to:route.query.uuid, 
+    to: route.query.uuid,
   };
   wsStore.sendMessage(message);
   text.value = "";
@@ -267,14 +324,14 @@ onMounted(() => {
     friendUsername: route.query.uuid,
   });
   scrollToBottom();
-  document.addEventListener('click', handleClickOutside);
+  document.addEventListener("click", handleClickOutside);
 });
 
 onUnmounted(() => {
   if (isMobile) Keyboard.removeAllListeners();
 });
 onBeforeUnmount(() => {
-  document.removeEventListener('click', handleClickOutside);
+  document.removeEventListener("click", handleClickOutside);
 });
 
 // 判断是否点击在元素外
@@ -287,7 +344,7 @@ const handleClickOutside = (event) => {
     showEmoji.value &&
     emojiEl &&
     !emojiEl.contains(target) &&
-    !target.closest('.emoji-toggle')
+    !target.closest(".emoji-toggle")
   ) {
     showEmoji.value = false;
   }
@@ -296,7 +353,7 @@ const handleClickOutside = (event) => {
     showTools.value &&
     toolsEl &&
     !toolsEl.contains(target) &&
-    !target.closest('.tools-toggle')
+    !target.closest(".tools-toggle")
   ) {
     showTools.value = false;
   }
@@ -508,16 +565,18 @@ const goDetail = () => router.push("detail");
   &.visible {
     transform: translateY(0);
   }
-  .tool-btn{
+  .tool-btn {
     margin-right: 32px;
     display: flex;
     flex-direction: column;
     align-items: center;
-    font-family: PingFang SC, PingFang SC;
+    font-family:
+      PingFang SC,
+      PingFang SC;
     font-weight: 400;
     font-size: 12px;
     color: #000000;
-    .tool-icon{
+    .tool-icon {
       width: 56px;
       height: 56px;
       margin-bottom: 4px;