用uniapp 及socket.io做一个简单聊天app 5

聊天中的通知及好友申请:

如下图效果

聊天的效果:

这里的friends,好友例表,里面有相关代码:

php 复制代码
<template>
  <view class="friends-container">
    <view v-if="!isLoggedIn" class="not-logged-in">
      <text>您尚未登录。请先登录以查看好友列表。</text>
      <button @click="goToLogin">去登录</button>
    </view>
    <view>
      <view v-if="friends.length === 0">
        <uni-list>
          <uni-list-chat
            :avatar-circle="true"
            title="增加好友"
            note="输入用户帐号或群号"
            :avatar="'../../static/addfriend.png'"
            showArrow
            link
            @click="gotadd"
          ></uni-list-chat>
        </uni-list>
      </view>
      <view>
        <uni-list>
          <uni-list-chat
            v-for="(friend, index) in friendsInv"
            :key="index"
            :title="friend.group ? friend.group.name : friend.user.username"
            :avatar-circle="true"
            :note="formatDate(friend.created_at) + ' 邀请'"
            badge-position="left"
            badge-text="邀请"
            showArrow
            link
            @tap="aggrees(friend.id)"
            :avatar="friend.group ? friend.group.avatar_url : friend.user.avatar_url"
          ></uni-list-chat>
        </uni-list>
      </view>
      <view v-if="friends.length > 0">
        <uni-list>
          <uni-list-chat
            :avatar-circle="true"
            title="增加好友"
            note="输入用户帐号或群号"
            :avatar="'../../static/addfriend.png'"
            showArrow
            link
            @click="gotadd"
          ></uni-list-chat>
        </uni-list>
        <uni-list>
          <uni-list-chat
            v-for="(friend, index) in friends"
            :key="index"
            :title="friend.type === 'group' ? '[群]' + friend.group.name : friend.user.username"
            :avatar-circle="true"
            :avatar="friend.type === 'group' ? friend.group.avatar_url : friend.user.avatar_url"
            :note="friend.message||'暂无聊天'"
            :time="friend.time"
            badge-position="left"
            :badge-text="friend.type === 'group' ? friend.count + '人' : ''"
            showArrow
            link
            @click="toChat(friend)"
          ></uni-list-chat>
        </uni-list>
      </view>
      <button @click="loadMoreFriends" v-if="hasMoreFriends">加载更多</button>
    </view>
    <uni-popup ref="popupBag" type="center">
      <view class="bagDetail">
        <view class="title flex align-center justify-content-between">
          <view class="flex-sub">添加好友</view>
          <view class="close-button" style="font-size: 22px" @tap="closepopupBag">×</view>
        </view>
        <uni-list :border="true">
          <uni-list-item title="邀请好友" note="请输入正确的帐号" badge-position="right" badge-text="dot" link @tap="goaddurl"></uni-list-item>
          <uni-list-item title="创建群" note="群号创建后不能修改" badge-position="right" badge-text="dot" link @tap="gogroupurl"></uni-list-item>
        </uni-list>
      </view>
    </uni-popup>
    <uni-popup ref="popupAggreeBag" type="center">
      <view class="bagDetail">
        <view class="title flex align-center justify-content-between">
          <view class="flex-sub">邀请操作</view>
        </view>
        <uni-list :border="true">
          <uni-list-item title="同意" note="同意后加入" badge-position="right" badge-text="dot" link @tap="aggree(1)"></uni-list-item>
          <uni-list-item title="拒绝" note="拒绝后则不再显示" badge-position="right" badge-text="dot" link @tap="aggree(0)"></uni-list-item>
        </uni-list>
      </view>
    </uni-popup>
    <button @click="myself">我的信息</button>
  </view>
</template>

<script>
import { mapState, mapActions } from 'vuex';
import io from 'socket.io-client';
import config from '@/config/config.js';

export default {
  data() {
    return {
      page: 1,
      perPage: 20,
      loading: false,
      hasMoreFriends: false,
      message: '',
      friends: [],
      friendsInv: [],
      aggreeid: '',
    };
  },

  computed: {
    ...mapState(['token', 'lastMessages']),
    isLoggedIn() {
      return !!this.token;
    },
  },

  onLoad() {
    this.invateFriends();
		this.loadFriends();
  },

  mounted() {
    this.socket = io(config.apiBaseUrl, {
      query: {
        token: this.token,
      },
    });
    this.socket.on('connect', () => {
      console.log('Socket connected:', this.socket.id);
    });
    this.socket.on('disconnect', () => {
      console.log('Socket disconnected');
    });

		this.socket.on('message', (msg) => {
			console.log('Received message:', msg);
    this.friends = this.friends.map((friend) => {
        if (friend.id == msg.group_name || (friend.group && friend.group.id == msg.group_name.replace("g_", ""))) {
          friend.message = msg.content;
        }
        return friend;
      });
			
			console.log(this.friends)
		});
		
		
  },
  beforeDestroy() {
    if (this.socket) {
      this.socket.off('message', this.handleNewMessage);
      this.socket.disconnect();
    }
  },
  methods: {
    ...mapActions(['fetchFriends']),
    handleNewMessage(msg) {
      this.friends = this.friends.map((friend) => {
        if (friend.id == msg.group_name || (friend.group && friend.group.id == msg.group_name.replace("g_", ""))) {
          friend.message = msg.content;
        }
        return friend;
      });
			console.log(this.friends)
			
    },
    async aggrees(id) {
      this.aggreeid = id;
      this.$refs.popupAggreeBag.open();
    },
    async aggree(agid) {
      try {
        this.$refs.popupAggreeBag.close();
        const token = uni.getStorageSync('token');
        const [error, response] = await uni.request({
          url: `${config.apiBaseUrl}/agree`,
          method: 'POST',
          header: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
          data: {
            id: this.aggreeid,
            status: agid === 1 ? 'accepted' : 'declined',
          },
        });
        if (response.data.code === 0) {
          uni.showToast({
            title: response.data.message,
            duration: 2000,
          });
          this.loadFriends();
          this.invateFriends();
        } else {
          uni.showToast({
            title: response.data.message,
          });
        }
      } catch (error) {
        console.error(error);
      }
    },
    formatDate(date) {
      const d = new Date(date);
      const year = d.getFullYear();
      const month = (d.getMonth() + 1).toString().padStart(2, '0');
      const day = d.getDate().toString().padStart(2, '0');
      const hours = d.getHours().toString().padStart(2, '0');
      const minutes = d.getMinutes().toString().padStart(2, '0');
      return `${year}年${month}月${day}日 ${hours}:${minutes}`;
    },
    async loadFriends() {
      const token = uni.getStorageSync('token');
      if (!token) return;
      try {
        const [error, response] = await uni.request({
          url: `${config.apiBaseUrl}/friends`,
          method: 'GET',
          header: {
            Authorization: `Bearer ${token}`,
          },
        });
        if (response.data) {
          this.friends = response.data.data;
        }
      } catch (error) {
        console.error(error);
      }
    },
    toChat(item) {
      const url = item.type === 'user'
        ? `/pages/index/chat?id=${item.id}&type=${item.type}&tid=${item.group_friend_id}`
        : `/pages/index/chat?id=g_${item.group_friend_id}&type=${item.type}&tid=${item.group_friend_id}`;
      uni.navigateTo({ url });
    },
    loadMoreFriends() {
      this.page++;
      this.loadFriends();
    },
    async invateFriends() {
      try {
        const token = uni.getStorageSync('token');
        const [error, response] = await uni.request({
          url: `${config.apiBaseUrl}/invateFriends`,
          method: 'GET',
          header: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
        });
        if (response.data.code === 0) {
          this.friendsInv = response.data.data.map(friend => ({
            ...friend,
            id: friend.id.toString(),
          }));
        } else {
          this.friendsInv = [];
        }
      } catch (error) {
        console.error(error);
      }
    },
    goToLogin() {
      uni.navigateTo({
        url: '/pages/index/login',
      });
    },
    gotadd() {
      this.$refs.popupBag.open();
    },
    goaddurl() {
      this.closepopupBag();
      uni.navigateTo({
        url: '/pages/index/addfriend',
      });
    },
    gogroupurl() {
      this.closepopupBag();
      uni.navigateTo({
        url: '/pages/index/addgroup',
      });
    },
    myself() {
      uni.navigateTo({
        url: '/pages/index/profile',
      });
    },
    closepopupBag() {
      this.$refs.popupBag.close();
    },
  },
};
</script>

<style>
.container {
  padding: 20px;
}

.bagDetail {
  padding: 10px;
  width: 100%;
  height: 30%;
  position: fixed;
  background-color: #ffffff;
  left: 0;
  display: flex;
  flex-direction: column;
}

#messages {
  height: 300px;
  overflow-y: scroll;
  border: 1px solid #ccc;
  margin-bottom: 10px;
}

input {
  display: block;
  margin: 10px 0;
}

button {
  display: block;
  margin: 10px 0;
}

.user-list {
  margin-top: 20px;
  border: 1px solid #ccc;
  padding: 10px;
}

.title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 10px;
}

.close-button {
  font-size: 22px;
  cursor: pointer;
}
</style>
相关推荐
Kapaseker42 分钟前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴1 小时前
Android17 为什么重写 MessageQueue
android
Mr_li16 小时前
给 Vue 开发者的 uni-app 快速指南
vue.js·uni-app
anyup19 小时前
🔥2026最推荐的跨平台方案:H5/小程序/App/鸿蒙,一套代码搞定
前端·uni-app·harmonyos
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android