Browse Source

添加群接口

wkw 1 month ago
parent
commit
d11d632374

+ 1 - 1
capacitor.config.ts

@@ -53,7 +53,7 @@ let config: CapacitorConfig = {
 // 开发服务器配置(热更新用)
 if (process.env.DAPP_BUILD != "1") {
   config.server = {
-    url: 'https://192.168.0.59:5173',
+    url: 'https://192.168.0.70:5173',
     cleartext: false,          // 允许HTTP明文通信(仅开发环境)
     androidScheme: 'https',  
     allowNavigation: ['*']    // 允许任意URL导航

+ 1 - 1
package.json

@@ -7,7 +7,7 @@
     "dev": "vite --host",
     "updata": "node ./updataSetVersion.js",
     "s": "cross-env DAPP_BUILD=1 npx cap sync",
-    "a": "npx cap run android --live-reload --host=192.168.0.59 --port=5173",
+    "a": "npx cap run android --live-reload --host=192.168.0.70 --port=5173",
     "app": "npx cap run android --live-reload --host=192.168.0.70 --port=5173",
     "ios": "npx cap run ios --live-reload --host=192.168.0.59 --port=5173",
     "android": "npx cap add android && npm run icon && npx cap sync",

+ 37 - 10
src/api/path/im.api.js

@@ -3,47 +3,47 @@ import httpRequest from '../axios'
 
 // 登录获取用户信息
 export function imLogin(param) {
-  return service.post('user/login', param);
+  return service.post('/user/login', param);
 }
 
 // 上传文件
 export function uploadFile(param) {
-  return service.post('file', param);
+  return service.post('/file', param);
 }
 
 // 修改用户信息
 export function userInfoEdit(param) {
-  return service.put('user', param );
+  return service.put('/user', param );
 }
 
 // 获取用户列表 
 export function userList(params) {
-  return service.get('user', { params});
+  return service.get('/user', { params});
 }
 
 // 添加好友
 export function userFriend(param) {
-  return service.post('friend', param);
+  return service.post('/friend', param);
 }
 
 // 获取用户详情
 export function userUuid(uuid) {
-  return service.get(`user/${uuid}`);
+  return service.get(`/user/${uuid}`);
 }
 
 // 获取消息
 export function getMessageApi(data) {
-  return service.post('message', data );
+  return service.post('/message', data );
 }
 
 // 获取好友申请审核列表
 export function friendRequest(params) {
-  return service.get("user/friend/request", { params });
+  return service.get("/user/friend/request", { params });
 }
 
 // 同意或者拒绝好友
 export function friendAudit(param) {
-  return service.post('user/friend/audit', param);
+  return service.post('/user/friend/audit', param);
 }
 
 // 获取群成员信息
@@ -53,7 +53,7 @@ export function groupList(uuid) {
 
 // 修改群名称
 export function selectGroupName(uuid, param) {
-  return service.post(`group/${uuid}`, param);
+  return service.post(`/group/${uuid}`, param);
 }
 
 // 创建群组
@@ -66,6 +66,33 @@ export function groupJoin(userUuid, groupUuid) {
   return service.post(`/group/join/${userUuid}/${groupUuid}`);
 }
 
+// 移出成员  userUuid:用户uuid    groupUuid:群uuid   selfUuid:群主uuid
+export function groupDisjoin(userUuid, groupUuid,selfUuid) {
+  return service.post(`/group/disjoin/${userUuid}/${groupUuid}/${selfUuid}`);
+}
+
+// 修改群个人名称
+export function selectEditnickname(uuid, param) {
+  return service.post(`/group/editnickname/${uuid}`, param);
+}
+
+// 解散群组
+export function dissolveGroup(selfUuid, groupId) {
+  return service.post(`/group/dissolveGroup/${selfUuid}/${groupId}`);
+}
+
+// 退出群组
+export function quitGroup(selfUuid, groupId) {
+  return service.post(`/group/quitGroup/${selfUuid}/${groupId}`);
+}
+
+// 删除好友
+export function delFriend(param) {
+  return service.post("/delFriend",param);
+}
+
+
+
 
 // *****************朋友圈接口**********************
 

+ 2 - 0
src/common/constant/msgType.js

@@ -6,6 +6,7 @@ export const MSG_TYPE = {
   VIDEO: 5, // 视频
   AUDIO_ONLINE: 6, // 音频
   VIDEO_ONLINE: 7, // 视频
+  JION_GROUP:99,//加入群
 };
 export const MSG_TYPE_MAP = [
   MSG_TYPE.TEXT,
@@ -15,6 +16,7 @@ export const MSG_TYPE_MAP = [
   MSG_TYPE.VIDEO,
   MSG_TYPE.AUDIO_ONLINE,
   MSG_TYPE.VIDEO_ONLINE,
+  MSG_TYPE.JION_GROUP
 ]
 
 export const MESSAGE_TYPE_USER = 1; // 单聊

+ 2 - 1
src/i18n/zhHk/router.js

@@ -39,5 +39,6 @@ export default {
   Search:"搜索",
   CreateGroupChat:"創建羣聊",
   WhiteSettings:"白名單設置",
-  Publish:"發佈"
+  Publish:"發佈",
+  SelectMembers:"選擇成員"
 };

+ 11 - 0
src/router/system.js

@@ -308,6 +308,17 @@ export const systemRoutes = [
         }, //  创建群聊
         component: () => import("@/views/im/detail/addMember/index.vue"),
       },
+      {
+        path: "deleteMember",
+        name: "deleteMember",
+        meta: {
+          title: "router.SelectMembers",
+          keepAlive: false,
+          navbar: true,
+          leftArrow: true,
+        }, //  移出成员
+        component: () => import("@/views/im/detail/deleteMember/index.vue"),
+      },
       {
         path: "checkMember",
         name: "checkMember",

+ 2 - 1
src/stores/modules/systemStore.js

@@ -12,7 +12,8 @@ export const useSystemStore = defineStore("useSystemStore", {
     DAPP_CACHE_KEY:[],
     AddressList:[],
     DeviceId:'',
-    USERID:""
+    USERID:"",
+    ImsessionList:[]
   }),
   persist: true,
   getters: {

+ 5 - 0
src/stores/modules/webSocketStore.js

@@ -51,6 +51,7 @@ export const useWebSocketStore = defineStore("webSocketStore", {
       currentAttempt: 0,
     },
     queueFlushTimer: null,
+    loading:false
   }),
 
   persist: {
@@ -410,6 +411,8 @@ export const useWebSocketStore = defineStore("webSocketStore", {
 
     // 获取历史消息
     async getMessages(params) {
+      this.messages = []
+      this.loading = true  // 开始加载
       try {
         const { data } = await getMessageApi({
           messageType: params.messageType,
@@ -428,6 +431,8 @@ export const useWebSocketStore = defineStore("webSocketStore", {
       } catch (error) {
         console.error("获取消息失败:", error);
         throw error;
+      } finally {
+        this.loading = false // 结束加载
       }
     },
   },

+ 9 - 2
src/views/im/chat/index.vue

@@ -13,6 +13,8 @@
 
     <!-- 聊天消息区域 -->
     <div class="chat-list" ref="chatListRef">
+      <van-loading class="load-box" size="24px" v-if="wsStore.loading">加载中...</van-loading>
+      <template v-else>
       <div v-for="(item, index) in wsStore.messages" :key="index">
         <div class="chat-time">
           {{ item.createDate || formatTime(Date.now()) }}
@@ -77,6 +79,7 @@
           </div>
         </div>
       </div>
+      </template>
     </div>
 
     <!-- 输入框 -->
@@ -165,10 +168,10 @@
           </van-uploader>
           <div>图片</div>
         </div>
-        <div class="tool-btn">
+        <!-- <div class="tool-btn">
           <svg-icon class="tool-icon" name="ps" />
           <div>拍摄</div>
-        </div>
+        </div> -->
         <div class="tool-btn" v-if="wsStore.toUserInfo.type == 'user'">
           <svg-icon
             class="tool-icon"
@@ -522,6 +525,10 @@ const goDetail = () => router.push({
 </script>
 
 <style lang="less" scoped>
+.load-box{
+  text-align: center !important;
+  margin-top: 50px !important;
+}
 .mr12 {
   margin-right: 12px;
 }

+ 55 - 41
src/views/im/contactList/index.vue

@@ -15,26 +15,29 @@
                     <div class="li-cont no-border">系统通知</div>
                 </div> -->
             </div>
-            <van-index-bar>
-                <template v-for="(group, groupIndex) in memberGroups" :key="group.index">
-                    <van-index-anchor :index="group.index" />
-                    <van-cell-group inset>
-                        <van-cell v-for="(item, index) in group.members" @click="goToPage(item.uuid)">
-                            <template #title>
-                                <div class="cell-item">
-                                    <van-image
-                                        class="cell-img"
-                                        round
-                                        :src="item.avatar"
-                                    />
-                                    <div>{{ item.nickname }}</div>
-                                </div>
-                            </template>
-                        </van-cell>
-                    </van-cell-group>
-                </template>
-            </van-index-bar>
-            <div class="total">{{memberGroups.length}}个朋友</div>
+            <van-loading class="load-box" size="24px" v-if="loading">加载中...</van-loading>
+            <template v-else>
+                <van-index-bar>
+                    <template v-for="(group, groupIndex) in memberGroups" :key="group.index">
+                        <van-index-anchor :index="group.index" />
+                        <van-cell-group inset>
+                            <van-cell v-for="(item, index) in group.members" @click="goToPage(item.uuid)">
+                                <template #title>
+                                    <div class="cell-item">
+                                        <van-image
+                                            class="cell-img"
+                                            round
+                                            :src="item.avatar"
+                                        />
+                                        <div>{{ item.nickname }}</div>
+                                    </div>
+                                </template>
+                            </van-cell>
+                        </van-cell-group>
+                    </template>
+                </van-index-bar>
+                <div class="total">{{memberGroups.length}}个朋友</div>
+            </template>
         </div>
     </div>
 </template>
@@ -49,32 +52,39 @@ const walletStore = useWalletStore();
 
 const memberGroups = ref([]);
 const groupArr = ref([]);
+const loading = ref(false);
 
 const getuserList = async () => {
-  const res = await userList({ uuid: walletStore.account });
-  const groupedMap = {};
-  groupArr.value = res.data.groups || [];//群聊数据
-  res.data?.userList?.forEach(user => {
-    const name = user.nickname || '';
-    user.avatar = user.avatar ? IM_PATH + user.avatar : user.avatar
-    const firstLetter = getFirstLetter(name); // 获取首字母
+  loading.value = true;
+  try {
+    const res = await userList({ uuid: walletStore.account });
+    const groupedMap = {};
+    groupArr.value = res.data.groups || [];//群聊数据
+    res.data?.userList?.forEach(user => {
+        const name = user.nickname || '';
+        user.avatar = user.avatar ? IM_PATH + user.avatar : user.avatar
+        const firstLetter = getFirstLetter(name); // 获取首字母
 
-    if (!groupedMap[firstLetter]) {
-      groupedMap[firstLetter] = [];
-    }
-
-    groupedMap[firstLetter].push({...user});
-  });
+        if (!groupedMap[firstLetter]) {
+        groupedMap[firstLetter] = [];
+        }
 
-  // 转为数组并按 index 排序
-  const groupedList = Object.keys(groupedMap)
-    .sort()
-    .map(letter => ({
-      index: letter,
-      members: groupedMap[letter]
-    }));
+        groupedMap[firstLetter].push({...user});
+        // 转为数组并按 index 排序
+        const groupedList = Object.keys(groupedMap)
+            .sort()
+            .map(letter => ({
+            index: letter,
+            members: groupedMap[letter]
+            }));
 
-  memberGroups.value = groupedList;
+        memberGroups.value = groupedList;
+    });
+  } catch (error) {
+    console.error(error);
+  } finally {
+    loading.value = false;
+  }
 };
 
 // 跳转群聊
@@ -104,6 +114,10 @@ onMounted(() => {
 </script>
 
 <style lang="less" scoped>
+.load-box{
+  text-align: center !important;
+  margin-top: 50px !important;
+}
 .container{
     height: 100%;
     display: flex;

+ 16 - 5
src/views/im/contactList/invitation/index.vue

@@ -6,7 +6,8 @@
                 <div class="tab-text" :class="type == 2?'active-color':''" @click="changeTab(2)">我发起的</div>
             </div>
             <div class="list-box">
-                <template v-if="list.length > 0">
+                <van-loading class="load-box" size="24px" v-if="loading">加载中...</van-loading>
+                <template v-else-if="list.length > 0">
                     <div class="box-item" :class="{ 'no-border': i === 9 }" v-for="(item,i) in list">
                         <van-image class="item-img" round src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"/>
                         <div class="item-ri-cont">
@@ -34,7 +35,7 @@
                         </div>
                     </div>
                 </template>
-                <template v-if="list.length == 0">
+                <template v-else>
                     <div class="no-more">
                         <svg-icon class="no-more-img" name="no-more" />
                         <div>暂无数据</div>
@@ -70,11 +71,17 @@ const walletStore = useWalletStore();
 const type = ref(1);
 const showAgree = ref(false);
 const list = ref([]);
-const activeItem = ref({})
+const activeItem = ref({});
+const loading = ref(false);
 
 const getfriendRequest = async () => {
-    const res = await friendRequest({uuid:walletStore.account,status:type.value});
-    list.value = res.data;
+    loading.value = true;
+    try {
+        const res = await friendRequest({ uuid: walletStore.account, status: type.value });
+        list.value = res.data || [];
+    } finally {
+        loading.value = false;
+    }
 }
 const changeBtn = (item) => {
     showAgree.value = true;
@@ -102,6 +109,10 @@ onMounted(()=>{
 </script>
 
 <style lang="less" scoped>
+.load-box{
+  text-align: center !important;
+  margin-top: 50px !important;
+}
 .container{
     height: 100%;
     display: flex;

+ 84 - 64
src/views/im/detail/addMember/index.vue

@@ -1,49 +1,51 @@
 <template>
     <div class="container">
-        <van-index-bar class="list">
-            <template v-for="(group, groupIndex) in memberGroups" :key="group.index">
-                <van-index-anchor :index="group.index" />
-                <van-checkbox-group v-model="checked">
-                    <van-cell-group inset>
-                        <van-cell
-                            v-for="(item, index) in group.members"
-                            :key="item.id"
-                            clickable
-                            @click="toggle(groupIndex, index, item)"
-                        >
-                            <template #icon>
-                                <van-checkbox checked-color="#4765DD"
-                                    :name="item"
-                                    :ref="el => setCheckboxRef(groupIndex, index, el)"
-                                    :disabled="joinedIds.has(item.uuid)"
-                                    @click.stop
-                                />
-                            </template>
-                            <template #title>
-                                <div class="cell-item">
-                                    <van-image class="img-icon" round :src="item.avatar" />
-                                    <div>{{ item.nickname }}</div>
-                                    <span v-if="joinedIds.has(item.uuid)" class="joined-tag">已在群</span>
-                                </div>
-                            </template>
-                        </van-cell>
-                    </van-cell-group>
-                </van-checkbox-group>
-            </template>
-        </van-index-bar>
-  
-        <div class="footer-box">
-            <div class="avatar-list">
-                <van-image
-                    v-for="item in checked"
-                    :key="item.id"
-                    class="img-icon"
-                    round
-                    :src="item.avatar"
-                />
+        <van-loading class="load-box" size="24px" v-if="loading">加载中...</van-loading>
+        <template v-else>
+            <van-index-bar class="list">
+                <template v-for="(group, groupIndex) in memberGroups" :key="group.index">
+                    <van-index-anchor :index="group.index" />
+                    <van-checkbox-group v-model="checked">
+                        <van-cell-group inset>
+                            <van-cell
+                                v-for="(item, index) in group.members"
+                                :key="item.id"
+                                clickable
+                                @click="toggle(groupIndex, index, item)"
+                            >
+                                <template #icon>
+                                    <van-checkbox checked-color="#4765DD"
+                                        :name="item"
+                                        :ref="el => setCheckboxRef(groupIndex, index, el)"
+                                        :disabled="joinedIds.has(item.uuid)"
+                                        @click.stop
+                                    />
+                                </template>
+                                <template #title>
+                                    <div class="cell-item">
+                                        <van-image class="img-icon" round :src="item.avatar" />
+                                        <div>{{ item.nickname }}</div>
+                                        <span v-if="joinedIds.has(item.uuid)" class="joined-tag">已在群</span>
+                                    </div>
+                                </template>
+                            </van-cell>
+                        </van-cell-group>
+                    </van-checkbox-group>
+                </template>
+            </van-index-bar>
+            <div class="footer-box">
+                <div class="avatar-list">
+                    <van-image
+                        v-for="item in checked"
+                        :key="item.id"
+                        class="img-icon"
+                        round
+                        :src="item.avatar"
+                    />
+                </div>
+                <div class="btn" :class="checked.length == 0?'':'active-btn'" @click="submit">完成({{ checked.length }})</div>
             </div>
-            <div class="btn" :class="checked.length == 0?'':'active-btn'" @click="submit">完成({{ checked.length }})</div>
-        </div>
+        </template>
     </div>
 </template>
   
@@ -54,6 +56,7 @@ import {getFirstLetter} from "@/utils/utils";
 import { useWalletStore } from "@/stores/modules/walletStore";
 import { useWebSocketStore } from "@/stores/modules/webSocketStore.js";
 import { showToast } from "vant";
+import { MSG_TYPE, MESSAGE_TYPE_USER,MESSAGE_TYPE_GROUP } from "@/common/constant/msgType";
 const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE;
 const router = useRouter();
 const route = useRoute();
@@ -63,6 +66,7 @@ const wsStore = useWebSocketStore();
 
 const checked = ref([]);
 const memberGroups = ref([]);
+const loading = ref(false);
 
 // 已加入群的成员数组
 const dataArr = route.query.arr ? JSON.parse(route.query.arr) : []; 
@@ -70,31 +74,35 @@ const dataArr = route.query.arr ? JSON.parse(route.query.arr) : [];
 const joinedIds = new Set(dataArr.map(m => m.uuid));
 
 const getuserList = async () => {
-  const res = await userList({ uuid: walletStore.account });
-  const groupedMap = {};
+    loading.value = true;
+    try {
+        const res = await userList({ uuid: walletStore.account });
+        const groupedMap = {};
 
-  res.data?.userList?.forEach(user => {
-    const name = user.nickname || '';
-    user.avatar = user.avatar ? IM_PATH + user.avatar : user.avatar
-    const firstLetter = getFirstLetter(name); // 获取首字母
+        res.data?.userList?.forEach(user => {
+            const name = user.nickname || '';
+            user.avatar = user.avatar ? IM_PATH + user.avatar : user.avatar
+            const firstLetter = getFirstLetter(name); // 获取首字母
 
-    if (!groupedMap[firstLetter]) {
-      groupedMap[firstLetter] = [];
-    }
+            if (!groupedMap[firstLetter]) {
+            groupedMap[firstLetter] = [];
+            }
 
-    groupedMap[firstLetter].push({...user});
-  });
+            groupedMap[firstLetter].push({...user});
+        });
 
-  // 转为数组并按 index 排序
-  const groupedList = Object.keys(groupedMap)
-    .sort()
-    .map(letter => ({
-      index: letter,
-      members: groupedMap[letter]
-    }));
+        // 转为数组并按 index 排序
+        const groupedList = Object.keys(groupedMap)
+            .sort()
+            .map(letter => ({
+            index: letter,
+            members: groupedMap[letter]
+            }));
 
-  memberGroups.value = groupedList;
-  console.log(memberGroups.value)
+        memberGroups.value = groupedList;
+    } finally {
+        loading.value = false;
+    }
 };
 
 const checkboxRefs = ref([]);
@@ -119,8 +127,9 @@ const toggle = (groupIndex, index, item) => {
 
 const submit = async () => {
     if(checked.value.length == 0) return;
-    console.log("已选择成员:", checked.value.uuid);
+    console.log("已选择成员:", checked.value);
     const ids = checked.value.map(item => item.id);//创建群需要id
+    const uname = checked.value.map(item => item.nickname).join('、');
     const uuids = checked.value.map(item => item.uuid);//添加成员需要uuid
     if(route.name == 'addMember'){
         // 添加成员加入群组
@@ -146,12 +155,19 @@ const submit = async () => {
                 userIds:ids
             });
             console.log(res)
+            const message = {
+                content: `你邀请${uname}加入了群聊`,
+                contentType: MSG_TYPE.JION_GROUP, // 99: 加入群
+                messageType: 2,
+            };
+            wsStore.sendMessage(message);
             if(res.code == 200){
                 router.push('im')
             }else{
                 showToast('创建失败')
             }
         } catch (error) {
+            console.log(error)
             showToast('创建失败')
         }
     }
@@ -162,6 +178,10 @@ onMounted(() => {
 </script>
 
 <style lang="less" scoped>
+.load-box{
+  text-align: center !important;
+  margin-top: 50px !important;
+}
 .container{
     display: flex;
     flex-direction: column;

+ 255 - 0
src/views/im/detail/deleteMember/index.vue

@@ -0,0 +1,255 @@
+<template>
+    <div class="container">
+      <!-- 加载中 -->
+      <van-loading class="load-box" size="24px" v-if="loading">加载中...</van-loading>
+  
+      <template v-else>
+        <van-index-bar class="list">
+          <template v-for="(group, groupIndex) in memberGroups" :key="group.index">
+            <van-index-anchor :index="group.index" />
+            <van-checkbox-group v-model="checked">
+              <van-cell-group inset>
+                <van-cell
+                  v-for="(item, index) in group.members"
+                  :key="item.id"
+                  clickable
+                  @click="toggle(groupIndex, index, item)"
+                >
+                  <template #icon>
+                    <van-checkbox
+                      checked-color="#4765DD"
+                      :name="item"
+                      :disabled="item.disabled"
+                      :ref="el => setCheckboxRef(groupIndex, index, el)"
+                      @click.stop
+                    />
+                  </template>
+                  <template #title>
+                    <div class="cell-item">
+                      <van-image class="img-icon" round :src="item.avatar" />
+                      <div>{{ item.nickname }}</div>
+                    </div>
+                  </template>
+                </van-cell>
+              </van-cell-group>
+            </van-checkbox-group>
+          </template>
+        </van-index-bar>
+  
+        <!-- 选择结果展示 -->
+        <div class="footer-box">
+          <div class="avatar-list">
+            <van-image
+              v-for="item in checked"
+              :key="item.id"
+              class="img-icon"
+              round
+              :src="item.avatar"
+            />
+          </div>
+          <div class="btn" :class="checked.length == 0 ? '' : 'active-btn'" @click="submit">
+            已选择({{ checked.length }})
+          </div>
+        </div>
+      </template>
+    </div>
+  </template>
+  
+  <script setup>
+  import { ref, onMounted, onBeforeUpdate } from 'vue';
+  import { groupList,groupDisjoin } from '@/api/path/im.api';
+  import { useWalletStore } from '@/stores/modules/walletStore';
+  import { useWebSocketStore } from "@/stores/modules/webSocketStore.js";
+  import { getFirstLetter } from '@/utils/utils';
+  import { showToast } from "vant";
+  
+  const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE;
+  const walletStore = useWalletStore();
+  const wsStore = useWebSocketStore();
+  const router = useRouter();
+  
+  const checked = ref([]);
+  const memberGroups = ref([]);
+  const loading = ref(false);
+  
+  const getUserList = async () => {
+    loading.value = true;
+    try {
+      const res = await groupList(wsStore.toUserInfo.uuid);
+
+      const groupedMap = {};
+      res.data?.forEach(user => {
+        const name = user.nickname || '';
+        user.avatar = user.avatar ? IM_PATH + user.avatar : user.avatar;
+
+        // 判断是否群主(guserId === userId)
+        if (user.guserId === user.userId) {
+          user.disabled = true; // 群主不可选
+        }
+
+        const firstLetter = getFirstLetter(name);
+        if (!groupedMap[firstLetter]) groupedMap[firstLetter] = [];
+        groupedMap[firstLetter].push({ ...user });
+      });
+
+      memberGroups.value = Object.keys(groupedMap)
+        .sort()
+        .map(letter => ({
+          index: letter,
+          members: groupedMap[letter]
+        }));
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  
+  // checkbox refs 用于点击切换
+  const checkboxRefs = ref([]);
+  onBeforeUpdate(() => {
+    checkboxRefs.value = [];
+  });
+  
+  const setCheckboxRef = (groupIndex, index, el) => {
+    if (!checkboxRefs.value[groupIndex]) checkboxRefs.value[groupIndex] = [];
+    checkboxRefs.value[groupIndex][index] = el;
+  };
+  
+  const toggle = (groupIndex, index, item) => {
+    if (item.disabled) return;
+    const checkbox = checkboxRefs.value[`${groupIndex}-${memberIndex}`];
+    if (checkbox) checkbox.toggle();
+  };
+  const submit = async () => {
+    if(checked.value.length == 0) return;
+    console.log("已选择成员:", checked.value);
+    const uuids = checked.value.map(item => item.uuid);//移出成员需要uuid
+     // 移出成员
+    try {
+      const res = await groupDisjoin(uuids,wsStore.toUserInfo.uuid,walletStore.account);
+      console.log(res)
+      if(res.code == 200){
+          showToast('移出成功')
+          router.push({
+              path: 'detail',
+              query:{ status:wsStore.toUserInfo.type == 'user'?1:2 }
+          }) // 1:单聊  2:群聊
+      }else{
+          showToast('移出失败')
+      }
+    } catch (error) {
+      console.log(error)
+      showToast('移出失败')
+    }
+  }
+  onMounted(() => {
+    getUserList();
+  });
+  </script>
+  
+<style lang="less" scoped>
+.load-box{
+  text-align: center !important;
+  margin-top: 50px !important;
+}
+.container{
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+}
+.list{
+    flex: 1;
+    overflow: auto;
+    :deep(.van-cell-group--inset){
+        border-radius:12px !important;
+    }
+    :deep(.van-cell){
+        padding: 12px 16px !important;
+    }
+    :deep(.van-cell:after){
+      left: 90px;
+    }
+    :deep(.van-index-anchor--sticky){
+        background: #F7F8FA !important;
+        color: #4765DD !important;
+    }
+    :deep(.van-index-bar__index){
+        font-weight: 400 !important;
+        font-size: 10px !important;
+        color: #1D2129 !important;
+        margin-bottom: 8px !important;
+    }
+    :deep(.van-index-bar__index--active){
+        color: #4765DD !important;
+    }
+    .cell-item{
+        display: flex;
+        align-items: center;
+        margin-left: 12px;
+        font-family: PingFang SC, PingFang SC;
+        font-weight: 500;
+        font-size: 15px;
+        color: #000000;
+        .img-icon{
+            width: 32px;
+            height: 32px;
+            flex-shrink: 0;
+            margin-right: 12px;
+        }
+        .joined-tag {
+          margin-left: 8px;
+          font-size: 12px;
+          color: #999;
+        }
+    }
+}
+.list::-webkit-scrollbar{
+    width: 0;
+}
+.footer-box {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-top: 16px;
+    height: 54px;
+    background: #ffffff;
+    padding: 0 16px;
+    box-sizing: border-box;
+    font-family: PingFang SC, PingFang SC;
+    font-weight: 400;
+    font-size: 15px;
+    color: #8d8d8d;
+
+    .avatar-list {
+        display: flex;
+        overflow-x: auto;
+        flex: 1;
+        .img-icon {
+            width: 32px;
+            height: 32px;
+            flex-shrink: 0;
+            margin-right: 4px;
+        }
+    }
+    .avatar-list::-webkit-scrollbar{
+        height: 0;
+    }
+    .btn{
+      margin-left: 16px;
+      height: 25px;
+      line-height: 25px;
+      padding: 0 6px;
+      background: #F2F2F2;
+      border-radius: 4px;
+      box-sizing: border-box;
+      font-family: PingFang SC, PingFang SC;
+      font-weight: 400;
+      font-size: 12px;
+      color: #8D8D8D;
+    }
+    .active-btn{
+      background: #4765DD;
+      color: #FFFFFF;
+    }
+}
+</style>

+ 78 - 16
src/views/im/detail/index.vue

@@ -20,10 +20,16 @@
                 </template>
                 <div class="member-li" @click="addMember" v-if="status == 2">
                     <div class="add">
-                        <svg-icon class="add-icon" name="add3" />
+                        <van-icon class="add-icon" name="plus" />
                     </div>
                     <div>邀请</div>
                 </div>
+                <div class="member-li" @click="deleteMember" v-if="status == 2 && isAdmin">
+                    <div class="add">
+                        <van-icon class="add-icon" name="minus" />
+                    </div>
+                    <div>移出</div>
+                </div>
             </div>
         </div>
         <van-cell-group class="user-bar-list" v-if="status == 2">
@@ -60,10 +66,10 @@
                 </template>
             </van-cell>
         </van-cell-group> -->
-        <van-cell-group class="user-bar-list" style="display: none;">
+        <van-cell-group class="user-bar-list">
             <van-cell class="user-bar-list-last" title="查看聊天记录" is-link @click="goChatDetail"></van-cell>
         </van-cell-group>
-        <div class="exit" @click="showExit = true" v-if="status == 2">退出群聊</div>
+        <div class="exit" @click="showExit = true" v-if="status == 2">{{isAdmin?'解散':'退出'}}群聊</div>
         <!-- 修改信息弹框 -->
         <van-popup v-model:show="showUpdateName" :style="{ borderRadius:'25px' }">
             <div class="pop-content-password">
@@ -95,10 +101,10 @@
         <!-- 退出群聊弹框 -->
         <van-popup v-model:show="showExit" :style="{ borderRadius:'25px' }">
             <div class="pop-content-password">
-                <div class="pop-title-password">确定要退出群聊吗?</div>
+                <div class="pop-title-password">确定要{{isAdmin?'解散':'退出'}}群聊吗?</div>
                 <div class="pop-btn-password" style="margin-top: 50px;">
                     <van-button type="default" class="btn-password cancel" @click="showExit = false">{{ $t('wallet.Cancel') }}</van-button>
-                    <van-button type="default" class="btn-password confirm" @click="popConfirm">{{ $t('wallet.Sure') }}</van-button>
+                    <van-button type="default" class="btn-password confirm" @click="popExit">{{ $t('wallet.Sure') }}</van-button>
                 </div>
             </div>
         </van-popup>
@@ -108,7 +114,7 @@
 <script setup>
 import { useWebSocketStore } from "@/stores/modules/webSocketStore.js";
 import { useWalletStore } from "@/stores/modules/walletStore";
-import { groupList,selectGroupName } from "@/api/path/im.api";
+import { groupList,selectGroupName,selectEditnickname,quitGroup,dissolveGroup } from "@/api/path/im.api";
 import { useRouter,useRoute } from 'vue-router'
 import { reactive } from 'vue'
 import { showNotify,showToast } from 'vant';
@@ -119,13 +125,12 @@ const wsStore = useWebSocketStore();
 const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE;
 const walletStore = useWalletStore(); 
 
-const newNickname = ref('');
 const groupMembers = ref([]);//显示部分成员
 const groupMembersNum= ref([]);//全部成员
 const information = reactive({
     name:wsStore.toUserInfo.nickname,
     notice:wsStore.toUserInfo.notice,
-    nickname:newNickname.value? newNickname.value : walletStore.username
+    nickname:''
 })
 const type = ref(1);//1:修改群聊名称  2:修改我的群昵称
 const disturb = ref(true);
@@ -141,18 +146,24 @@ const fetchGroupMembers = async () => {
   try {
     const res = await groupList(wsStore.toUserInfo.uuid);
     groupMembers.value = (res.data || []).slice(0, 8);
-    groupMembersNum.value = res.data || []
+    groupMembersNum.value = res.data || [];
+    const dataInfo = groupMembersNum.value.find(
+      m => m.uuid === walletStore.account
+    );
+    if (dataInfo) {
+      information.nickname = dataInfo.nickname || ''; // 群个人昵称
+    }
   } catch (e) {
     console.error("获取群成员失败", e);
   }
 }
+const isAdmin = computed(() => {
+  const self = groupMembersNum.value.find(m => m.uuid === walletStore.account);
+  return self?.userId === self?.guserId;
+});
 const changeUpdateName = (val) => {
     type.value = val;
     newName.value = val == 1?information.name:information.nickname;
-    if(val == 2){
-        showToast('无法修改!')
-        return;
-    };
     showUpdateName.value = true;
 }
 // 群聊名称取消
@@ -175,7 +186,20 @@ const popConfirm = () => {
             // 修改群聊名称接口
             funSelectGroupName({name:newName.value,uuid:wsStore.toUserInfo.uuid});
         }else{
+            // 修改群个人昵称接口
+            funSelectEditnickname({nickname:newName.value,groupId:wsStore.toUserInfo.id})
             information.nickname = newName.value;
+            // 同步更新 groupMembers 和 groupMembersNum(因为你两个地方用到)
+            const selfData = groupMembersNum.value.find(m => m.uuid === walletStore.account);
+            if (selfData) {
+                selfData.nickname = newName.value;
+            }
+
+            // 如果 groupMembers 是前 8 个成员的子集,也要更新
+            const selfInMembers = groupMembers.value.find(m => m.uuid === walletStore.account);
+            if (selfInMembers) {
+                selfInMembers.nickname = newName.value;
+            }
         }
     }, 1000);
 }
@@ -192,6 +216,20 @@ const funSelectGroupName = async (params) => {
         showToast('修改失败')
     }  
 }
+// 修改群个人昵称接口
+const funSelectEditnickname = async (params) => {
+    try {
+        const res = await selectEditnickname(wsStore.uuid,params);
+        if(res.code == 200){
+            showToast('修改成功');
+
+        }else{
+            showToast('修改失败')
+        }
+    } catch (error) {
+        showToast('修改失败')
+    }
+}
 // 群公告
 const changeUpdateNotice = () => {
     if(!newNotice.value){
@@ -206,6 +244,25 @@ const changeUpdateNotice = () => {
         wsStore.toUserInfo.notice  = newNotice.value;
     }, 500);
 }
+//退出或者解散
+const popExit = async () => {
+  const action = isAdmin.value ? dissolveGroup : quitGroup;
+  const successMsg = isAdmin.value ? '解散成功' : '退出成功';
+  const failMsg = isAdmin.value ? '解散失败' : '退出失败';
+
+  try {
+    const res = await action(walletStore.account, wsStore.toUserInfo.id);
+    if (res.code === 200) {
+      showToast(successMsg);
+      router.push('im');
+    } else {
+      showToast(failMsg);
+    }
+  } catch (error) {
+    showToast(failMsg);
+  }
+};
+
 // 跳转群二维码页面
 const goQrcode = () => {
     router.push('qrcode')
@@ -223,6 +280,12 @@ const addMember = () => {
         }
     })
 }
+// 跳转移出成员页面
+const deleteMember = () => {
+    router.push({
+        path: 'deleteMember'
+    })
+}
 // 跳转查看成员页面
 const checkMember = () => {
     router.push('checkMember')
@@ -304,11 +367,11 @@ onMounted(() => {
                     background: #F2F2F2;
                     position: relative;
                     .add-icon{
-                        width: 20px;
-                        height: 20px;
+                        font-size: 20px;
                         position: absolute;
                         top:50%;
                         left: 50%;
+                        color: #8D8D8D;
                         transform: translate(-50%,-50%);
                     }
                 }
@@ -365,7 +428,6 @@ onMounted(() => {
         color: #FF0000;
         text-align: center;
         line-height: 44px;
-        display: none;
     }
     .pop-content-password{
         padding: 27px 35px 25px 34px;

+ 27 - 21
src/views/im/index.vue

@@ -25,13 +25,13 @@
       </div>
     </div>
     <template v-if="activeTab === 0">
-      <div class="search" @click="goSearch">
+      <!-- <div class="search" @click="goSearch">
         <svg-icon class="search-icon" name="search" />
         <span>请输入关键词</span>
-      </div>
+      </div> -->
       <van-pull-refresh v-model="loading" @refresh="onRefresh" style="height:100%">
       <div class="message-list">
-        <div class="list-item" v-for="(item,i) in list" :key="i" @click="goToChat(item)">
+        <div class="list-item" v-for="(item,i) in list" :key="i" @click="goToChat(item,i)">
           <!-- 个人头像 -->
           <van-image class="item-img" round :src="item.avatar" v-if="item.type == 'user'"/>
           <!-- 群聊头像 -->
@@ -88,12 +88,13 @@ import Discover from './components/Discover/Discover.vue'
 import groupAvatar from './components/groupAvatar/index.vue'
 import { useWebSocketStore } from "@/stores/modules/webSocketStore.js";
 import { startScan } from "@/composables/barcodeScanner.js"
-import {setLocalStorage,getLocalStorage} from "../../utils/storage.js"
 import { eventBus } from '../../utils/utils';
+import { useSystemStore } from "@/stores/modules/systemStore";
 const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE;
 
 
-const walletStore = useWalletStore(); 
+const walletStore = useWalletStore();
+const systemStore = useSystemStore();
 const router = useRouter();
 const wsStore = useWebSocketStore();
 
@@ -105,13 +106,13 @@ const list = ref([]);
 const notice = ref(false)
 
 const onRefresh = () => {
-    loading.value = true;
     getuserList(true);
 };
 
 const getuserList = async (refresh=false) => {
   let res
-  if(refresh||!getLocalStorage('imsessionlist')){
+  if(refresh||!systemStore.ImsessionList){
+    loading.value = true;
     res = await userList({uuid:walletStore.account});
     loading.value = false;
     const groups = res.data.groups || [];
@@ -141,10 +142,9 @@ const getuserList = async (refresh=false) => {
       type: "user"
     }));
     list.value = [...groupItems, ...userItems];
-    setLocalStorage('imsessionlist',list.value)
+    systemStore.ImsessionList = list.value;
   }else{
-    list.value = getLocalStorage('imsessionlist')
-
+    list.value = systemStore.ImsessionList
   }
  
   for(var i in list.value){
@@ -155,8 +155,8 @@ const getuserList = async (refresh=false) => {
   }
 }
 
-const goToChat = (item) => {
-  console.log(item)
+const goToChat = (item,index) => {
+  systemStore.ImsessionList[index].unReadNum = 0
   wsStore.toUserInfo = item;
   router.push({
     path: 'chat',
@@ -182,28 +182,32 @@ const goSearch = () => {
 onMounted(()=>{
   getuserList();
   eventBus.$on('newMessage', (payload) => {
+    if(payload.contentType == 99){
+      getuserList(true);
+      return;
+    }
     // 处理数据
     if(payload){
       if(payload.type=='heatbeat')
       return 
       switch (payload.contentType){
         case 2:
-          payload.content=='[文件]'
+          payload.content='[文件]'
           break;
         case 3:
-          payload.content=='[图片]'
+          payload.content='[图片]'
           break;
         case 4:
-          payload.content=='[音频]'
+          payload.content='[音频]'
           break;
         case 5:
-          payload.content=='[视频]'
+          payload.content='[视频]'
           break;
         case 6:
-          payload.content=='[语音通话]'
+          payload.content='[语音通话]'
           break;
         case 7:
-          payload.content=='[视频通话]'
+          payload.content='[视频通话]'
           break;
       }
       var lists=list.value
@@ -214,8 +218,8 @@ onMounted(()=>{
         }
       }
       list.value=lists
-      showDot.value=true
-      setLocalStorage('imsessionlist',lists)
+      showDot.value=true;
+      systemStore.ImsessionList = lists;
     }
   });
 })
@@ -294,8 +298,10 @@ onMounted(()=>{
     border-radius: 30px 30px 0 0;
     flex: 1;
     overflow: auto;
-    margin-top: 16px;
+    // margin-top: 16px;
     padding: 26px 16px 0;
+    height: 100%;
+    box-sizing: border-box;
     .list-item{
       margin-bottom: 26px;
       display: flex;