Forráskód Böngészése

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

congjiang 1 hónapja
szülő
commit
775cba1ea3

+ 11 - 0
src/router/im.js

@@ -201,6 +201,17 @@ export const imRoutes = [
                 }, //  聊天搜索或者朋友圈搜索页面
                 component: () => import("@/views/im/chatSearch/index.vue"),
             },
+            {
+                path: "/videoPlayer",
+                name: "videoPlayer",
+                meta: {
+                    title: "",
+                    keepAlive: false,
+                    navbar: false,
+                    leftArrow: true,
+                }, //  聊天搜索或者朋友圈搜索页面
+                component: () => import("@/views/im/videoPlayer/index.vue"),
+            },
         ]
     }
 ]

+ 11 - 0
src/router/system.js

@@ -429,6 +429,17 @@ export const systemRoutes = [
         }, //  聊天搜索或者朋友圈搜索页面
         component: () => import("@/views/im/chatSearch/index.vue"),
       },
+      {
+        path: "/videoPlayer",
+        name: "videoPlayer",
+        meta: {
+          title: "",
+          keepAlive: false,
+          navbar: false,
+          leftArrow: true,
+        }, //  聊天搜索或者朋友圈搜索页面
+        component: () => import("@/views/im/videoPlayer/index.vue"),
+      },
     ],
   },
 ];

+ 52 - 3
src/views/im/components/Discover/Discover.vue

@@ -41,12 +41,27 @@
                 </div>
 
                 <!-- 视频 -->
-                <video v-if="item.video"
+                <!-- <video v-if="item.video"
                   :src="item.video"
                   controls
-                  width="200"
+                  width="200" height="300"
                   style="border-radius:8px; margin-top:8px;">
-                </video>
+                </video> -->
+                <!-- 视频封面 + 播放按钮 -->
+                <div v-if="item.video" class="course-video" @click="handlePlay(item)">
+                  <!-- 播放按钮蒙层 -->
+                  <div class="relative-shadow">
+                    <van-icon name="play-circle-o" class="play-icon"/>
+                  </div>
+                  <!-- 封面图 -->
+                  <van-image
+                    :src="item.video_thumb || defaultVideoThumb"
+                    fit="cover"
+                    width="200"
+                    height="300"
+                    class="video-thumb"
+                  />
+                </div>
 
                 <!-- 底部点赞评论按钮 -->
                 <div class="item-footer">
@@ -205,6 +220,16 @@ export default {
         startPosition: imgIndex,
       });
     },
+    handlePlay(item) {
+      console.log(item)
+      this.$router.push({
+        path: '/videoPlayer',
+        query: {
+          name: item.filename,
+          src: encodeURIComponent(item.video)
+        }
+      })
+    },
     gotoRecord() {
       this.$router.push('record');
     },
@@ -493,4 +518,28 @@ export default {
     margin: 0 40px !important;
     width: auto !important;
 }
+.course-video {
+  position: relative;
+  display: inline-block;
+  overflow: hidden;
+  border-radius: 8px;
+}
+
+.relative-shadow {
+  position: absolute;
+  top: 0; left: 0; right: 0; bottom: 0;
+  background: rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 200px;
+  height: 300px;
+  z-index: 1;
+  border-radius: 8px;
+}
+
+.play-icon {
+  font-size: 40px;
+  color: #fff;
+}
 </style>

+ 5 - 4
src/views/im/detail/chatLog/index.vue

@@ -5,7 +5,7 @@
                 <div v-for="(item,i) in tabList" :key="i"
                     class="tab-text" 
                     :class="type == i?'active-color':''" 
-                    @click="type = i">{{ item }}
+                    @click="changeTab(i)">{{ item }}
                 </div>
             </div>
             <div class="list-box">
@@ -103,9 +103,10 @@ const getMessage = async () => {
         console.log(error)
     }
 }
-watch(() => {
-  getMessage();
-});
+const changeTab = (i) => {
+    type.value = i;
+    getMessage();
+}
 const formatAvatarUrl = (item) => {
   const url = item?.sender?.avatar || item?.fromAvatar || ''
   if (/^https?:\/\//.test(url)) {

+ 3 - 0
src/views/im/detail/index.vue

@@ -274,6 +274,7 @@ const popConfirm = () => {
                 groupId: wsStore.toUserInfo.groupId
             });
             information.nickname = newName.value;
+            wsStore.toUserInfo.nickname = newName.value;;
             // 同步更新 groupMembers 和 groupMembersNum(因为你两个地方用到)
             const selfData = groupMembersNum.value.find(
                 m => m.uuid === walletStore.account
@@ -395,6 +396,7 @@ const changeDisturb = async () => {
     try {
         const res = await setMessageDisturb(wsStore.toUserInfo.uuid,{friendUuid:walletStore.account});
         if(res.code == 200){
+            showToast('设置成功');
             wsStore.toUserInfo.slience = res.data;
             wsStore.updateSessionNewMessage({slience: res.data}, wsStore.toUserInfo.uuid);
         }else{
@@ -409,6 +411,7 @@ const changeStory = async () => {
     try {
         const res = await stickChat({friendUuid:walletStore.account,uuid:wsStore.toUserInfo.uuid});
         if(res.code == 200){
+            showToast('设置成功');
             wsStore.toUserInfo.stick = res.data;
             wsStore.updateSessionNewMessage({stick: res.data}, wsStore.toUserInfo.uuid);
         }else{

+ 2 - 1
src/views/im/detail/qrcode/index.vue

@@ -1,7 +1,8 @@
 <template>
     <div class="container">
         <div class="content">
-            <groupAvatar class="img-icon" :users='wsStore.toUserInfo.avatars'></groupAvatar>
+            <div class="img-icon"></div>
+            <!-- <groupAvatar class="img-icon" :users='wsStore.toUserInfo.avatars'></groupAvatar> -->
             <div class="title">群聊:{{wsStore.toUserInfo.sessionName}}</div>
             <qrcode-vue
                 :value="qrtext"

+ 209 - 28
src/views/im/publish/index.vue

@@ -10,14 +10,73 @@
 
     <div style="margin-top: 16px;"></div>
 
-    <!-- 只上传图片 -->
-    <van-uploader
-      v-model="fileList"
-      :after-read="afterRead"
-      :max-count="9"
-      multiple
-      accept="image/*"
-    />
+    <!-- 上传类型选择 -->
+    <div v-if="!uploadType" class="upload-type">
+      <van-button type="default" @click="setUploadType('image')">上传图片</van-button>
+      <van-button type="default" @click="setUploadType('video')">上传视频</van-button>
+    </div>
+
+    <!-- 图片上传 -->
+    <div v-if="uploadType === 'image'">
+      <van-uploader
+        v-model="fileList"
+        :after-read="afterRead"
+        :max-count="9"
+        multiple
+        accept="image/*"
+      />
+    </div>
+    <van-button class="btn-cancel" v-if="uploadType === 'image'"
+      type="danger"
+      plain
+      size="large"
+      @click="cancelImageUpload"
+    >
+      取消上传图片
+    </van-button>
+
+    <!-- 视频上传 -->
+    <div v-if="uploadType === 'video'" class="preview-list">
+      <div v-if="videoFile.status === 'done'" class="preview-item">
+        <video controls :src="videoFile.url"></video>
+        <van-icon name="close" class="close-icon" @click="removeVideo" />
+      </div>
+
+      <!-- 上传中 -->
+      <div v-else-if="videoFile.status === 'uploading'" class="preview-item">
+        <van-loading size="24px" type="spinner">上传中...</van-loading>
+      </div>
+
+      <!-- 上传失败 -->
+      <div v-else-if="videoFile.status === 'failed'" class="preview-item">
+        <span style="color:red;">上传失败</span>
+        <van-button size="small" type="primary" @click="triggerVideoInput">
+          重新上传
+        </van-button>
+      </div>
+
+      <!-- 初始状态 -->
+      <div v-else class="van-uploader__upload" @click="triggerVideoInput">
+        <van-icon name="plus" size="24"/>
+      </div>
+
+      <input
+        type="file"
+        ref="videoInput"
+        accept="video/*"
+        @change="handleVideoChange"
+        style="display:none"
+      />
+    </div>
+    <!-- 取消按钮 -->
+    <van-button class="btn-cancel" v-if="uploadType === 'video'"
+      type="danger"
+      plain
+      size="large"
+      @click="cancelVideoUpload"
+    >
+      取消上传视频
+    </van-button>
 
     <!-- 发布按钮 -->
     <van-button
@@ -35,22 +94,37 @@
 </template>
 
 <script setup>
-import { ref, computed } from "vue";
 import { addDynamic, hostUploadImg } from "@/api/path/im.api";
 import { showToast } from "vant";
 
 const router = useRouter();
 const message = ref("");
-const fileList = ref([]);
+const fileList = ref([]); 
+const videoFile = ref({}); 
 const loading = ref(false);
-const uploading = ref(false); // 标记是否正在上传
+const uploading = ref(false);
+const videoUploading = ref(false);
+const videoInput = ref(null); 
+const uploadType = ref("");
 
-// 按钮禁用:没有文字 && 没有文件 时禁用;或者正在上传时禁用
-const isDisabled = computed(() => {
-  return uploading.value || !(message.value.trim() || fileList.value.length > 0);
-});
+// 设置上传类型
+const setUploadType = (type) => {
+  uploadType.value = type;
+};
+
+// 取消图片上传
+const cancelImageUpload = () => {
+  fileList.value = [];
+  uploadType.value = "";
+};
 
-// 选中文件后的处理逻辑(支持多选上传)——逐个上传
+// 取消视频上传
+const cancelVideoUpload = () => {
+  videoFile.value = {};
+  uploadType.value = "";
+};
+
+// 图片上传
 const afterRead = async (file) => {
   const files = Array.isArray(file) ? file : [file];
   uploading.value = true;
@@ -60,7 +134,6 @@ const afterRead = async (file) => {
     const formData = new FormData();
     formData.append("image", rawFile);
 
-    // 先标记状态
     f.status = "uploading";
 
     try {
@@ -77,26 +150,83 @@ const afterRead = async (file) => {
   uploading.value = false;
 };
 
-// 发布按钮逻辑
+// 点击上传视频
+const triggerVideoInput = () => {
+  if (videoInput.value) {
+    videoInput.value.value = null; 
+    videoInput.value.click();
+  }
+};
+
+// 视频选择与上传
+const handleVideoChange = async (event) => {
+  const file = event.target.files[0];
+  if (!file) return;
+
+  videoFile.value = {
+    file,
+    status: "uploading",
+    url: URL.createObjectURL(file),
+  };
+
+  const formData = new FormData();
+  formData.append("image", file);
+
+  try {
+    const res = await hostUploadImg(formData);
+    videoFile.value = {
+      file,
+      url: res.data.all_url,
+      status: "done",
+    };
+  } catch (err) {
+    console.error("视频上传失败:", err);
+    showToast("视频上传失败");
+    videoFile.value.status = "failed";
+  }
+};
+
+// 删除视频
+const removeVideo = () => {
+  videoFile.value = {};
+};
+
+// 按钮禁用
+const isDisabled = computed(() => {
+  return (
+    uploading.value ||
+    videoUploading.value ||
+    !(message.value.trim() || fileList.value.length > 0 || videoFile.value.url)
+  );
+});
+
+// 发布逻辑
 const confirm = async () => {
-  // 检查是否有图片没上传完
-  if (fileList.value.some((item) => item.status === "uploading")) {
-    showToast("请等待图片上传完成");
+  if (
+    fileList.value.some((item) => item.status === "uploading") ||
+    videoFile.value.status === "uploading"
+  ) {
+    showToast("请等待上传完成");
     return;
   }
 
   let params = {
     content: message.value,
-    type: "0", // 图片
-    images: "",
+    type: uploadType.value === "video" ? "1" : "0",
+    images: [],
+    video: "",
   };
 
-  if (fileList.value.length > 0) {
+  if (uploadType.value === "image" && fileList.value.length > 0) {
     params.images = fileList.value
-      .filter((item) => item.status === "done") // 只取成功的
+      .filter((item) => item.status === "done")
       .map((item) => item.url);
   }
 
+  if (uploadType.value === "video" && videoFile.value.status === "done") {
+    params.video = videoFile.value.url;
+  }
+
   loading.value = true;
   try {
     const res = await addDynamic(params);
@@ -104,6 +234,8 @@ const confirm = async () => {
       showToast("发布成功");
       message.value = "";
       fileList.value = [];
+      videoFile.value = {};
+      uploadType.value = "";
       router.back();
     } else {
       showToast(res.message || "发布失败");
@@ -119,6 +251,13 @@ const confirm = async () => {
 <style lang="less" scoped>
 .container {
   margin: 16px;
+  overflow: auto;
+
+  .upload-type {
+    display: flex;
+    justify-content: space-around;
+    // margin-bottom: 16px;
+  }
 
   :deep(.van-cell) {
     border-radius: 12px !important;
@@ -138,10 +277,20 @@ const confirm = async () => {
 
   :deep(.van-uploader__upload) {
     background: #f2f2f2 !important;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 100px;
+    height: 100px;
+    border-radius: 12px;
+    font-size: 16px;
+    color: #8d8d8d;
+    cursor: pointer;
   }
 
   .btn {
-    margin-top: 40px;
+    margin-top: 20px;
     background: #4765dd;
     height: 40px;
     line-height: 40px;
@@ -154,9 +303,15 @@ const confirm = async () => {
     color: #ffffff;
     border: none !important;
   }
+  .btn-cancel{
+    margin-top: 20px;
+    height: 40px;
+    line-height: 40px;
+    border-radius: 50px;
+  }
 
   .preview-list {
-    margin-top: 20px;
+    margin-top: 10px;
     display: flex;
     flex-wrap: wrap;
     gap: 10px;
@@ -164,6 +319,32 @@ const confirm = async () => {
 
   .preview-item {
     position: relative;
+    width: 100%;
+    height: 300px;
+    border-radius: 12px;
+    overflow: hidden;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #f2f2f2;
+
+    video {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+
+    .close-icon {
+      position: absolute;
+      top: 4px;
+      right: 4px;
+      font-size: 20px;
+      color: #fff;
+      background: rgba(0, 0, 0, 0.5);
+      border-radius: 50%;
+      padding: 2px;
+      cursor: pointer;
+    }
   }
 }
-</style>
+</style>

+ 93 - 0
src/views/im/videoPlayer/index.vue

@@ -0,0 +1,93 @@
+<template>
+    <div class="video-model">
+      <!-- 返回按钮 -->
+      <div class="header">
+        <van-button type="primary" size="small" @click="goBack">关闭视频</van-button>
+      </div>
+  
+      <!-- 视频全屏播放 -->
+      <video
+        ref="videoRef"
+        class="video-box"
+        :src="url"
+        controls
+        autoplay
+      ></video>
+  
+      <!-- 底部操作按钮 -->
+      <!-- <div class="opt-model">
+        <van-button round type="success" @click="download">保存到本地</van-button>
+      </div> -->
+    </div>
+  </template>
+  
+  <script setup>
+//   import { useRoute, useRouter } from 'vue-router'
+//   import { Filesystem, Directory } from '@capacitor/filesystem'
+//   import { Share } from '@capacitor/share'
+  
+  const route = useRoute()
+  const router = useRouter()
+  const url = decodeURIComponent(route.query.src || '');
+  const name = route.query.name || 'video.mp4'
+  
+  function goBack() {
+    router.back()
+  }
+  
+//   async function download() {
+//     try {
+//       const response = await fetch(url)
+//       const blob = await response.blob()
+//       const reader = new FileReader()
+//       reader.onload = async () => {
+//         const base64Data = (reader.result).split(',')[1]
+//         await Filesystem.writeFile({
+//           path: name,
+//           data: base64Data,
+//           directory: Directory.Documents
+//         })
+//         await Share.share({
+//           title: '视频已保存',
+//           text: '点击查看视频',
+//           url: url
+//         })
+//       }
+//       reader.readAsDataURL(blob)
+//     } catch (err) {
+//       console.error('下载失败', err)
+//     }
+//   }
+  </script>
+  
+  <style scoped lang="less">
+  .video-model {
+    background: #000;
+    width: 100%;
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+  }
+  
+  .header {
+    padding: 8px;
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    z-index: 10;
+  }
+  
+  .video-box {
+    flex: 1;
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+  
+  .opt-model {
+    position: absolute;
+    bottom: 20px;
+    right: 20px;
+  }
+  </style>
+