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

news2024/11/16 9:29:46

界面如下:
在这里插入图片描述

<template>
  <view class="container">
    <input v-model="username" placeholder="用户名" />
    <input v-model="password" type="password" placeholder="密码" />
    <button @click="handleLogin">登录</button>
    <text v-if="errorMessage" class="error">{{ errorMessage }}</text>
    <view class="link">
      <text>没有帐号?</text>
      <button @click="goreg">注册</button>
    </view>
  </view>
</template>

<script>
import { mapActions } from 'vuex';

export default {
  data() {
    return {
      username: '',
      password: '',
      errorMessage: ''
    };
  },
  methods: {
    ...mapActions(['login', 'fetchUser']),
	 validateInput() {
	      const usernameRegex = /^[a-zA-Z0-9]{6,12}$/;
	      const passwordRegex = /^[a-zA-Z0-9]{6,12}$/;
	
	      if (!usernameRegex.test(this.username)) {
	        this.errorMessage = '用户名必须是6到12位的字母或数字';
	        return false;
	      }
	
	      if (!passwordRegex.test(this.password)) {
	        this.errorMessage = '密码必须是6到12位的字母或数字';
	        return false;
	      }
	
	      return true;
	    },
	
	
    async handleLogin() {
		
		if (!this.validateInput()) {
		        return;
	     }
		
		
      try {
		//用户名 6~12位  密码 6~12位  
		  
		  
        console.log('Attempting login...');
        await this.login({ username: this.username, password: this.password });
     
      } catch (error) {
 
        this.errorMessage = '登陆失败';
      }
    },
    goreg() {
      uni.navigateTo({
        url: '/pages/index/register'
      });
    }
  },
  async mounted() {
    const token = uni.getStorageSync('token');
    if (token) {
      try {
        // Attempt to fetch user data with the token
        await this.fetchUser();
        // Redirect to the friends page if the user is authenticated
        uni.redirectTo({
          url: '/pages/index/friends'
        });
      } catch (error) {
        console.error('Failed to fetch user:', error);
        this.errorMessage = '自动登录失败,请重新登录';
      }
    }
  }
};
</script>

<style>
.container {
  padding: 20px;
}
input {
  display: block;
  margin: 10px 0;
}
button {
  display: block;
  margin: 10px 0;
}
.error {
  color: red;
}
.link {
  margin-top: 20px;
  text-align: center;
}
.link button {
  background-color: #007aff;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 5px;
}
</style>

注册页:
在这里插入图片描述

<template>
  <view class="container">
    <input v-model="username" placeholder="用户名" />
    <input v-model="password" type="password" placeholder="密码" />
    <button @click="register">注册</button>
    <text v-if="errorMessage" class="error">{{ errorMessage }}</text>
    <view class="link">
      <text>已有帐号?</text>
      <button @click="goToLogin">登录</button>
    </view>
  </view>
</template>

<script>
import config from '@/config/config.js';

export default {
  data() {
    return {
      username: '',
      password: '',
      errorMessage: ''
    };
  },
  methods: {
    validateInput() {
      const usernameRegex = /^[a-zA-Z0-9]{6,12}$/;
      const passwordRegex = /^[a-zA-Z0-9]{6,12}$/;

      if (!usernameRegex.test(this.username)) {
        this.errorMessage = '用户名必须是6到12位的字母或数字';
        return false;
      }

      if (!passwordRegex.test(this.password)) {
        this.errorMessage = '密码必须是6到12位的字母或数字';
        return false;
      }

      return true;
    },
    async register() {
      if (!this.validateInput()) {
        return;
      }

      try {
        const [error, response] = await uni.request({
          url: config.apiBaseUrl + '/register',
          method: 'POST',
          data: {
            username: this.username,
            password: this.password
          }
        });

        if (response.data.success) {
          uni.navigateTo({
            url: '/pages/index/login'
          });
          this.errorMessage = ''; // 清除任何以前的错误消息
        } else {
          this.errorMessage = response.data.error;
        }
      } catch (error) {
        console.error(error);
        this.errorMessage = '发生错误';
      }
    },
    goToLogin() {
      uni.navigateTo({
        url: '/pages/index/login'
      });
    }
  }
};
</script>

<style>
.container {
  padding: 20px;
}
input {
  display: block;
  margin: 10px 0;
}
button {
  display: block;
  margin: 10px 0;
}
.error {
  color: red;
}
.link {
  margin-top: 20px;
  text-align: center;
}
.link button {
  display: block;
  background-color: #007aff;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 5px;
}
</style>

在store加入index.js:

import Vue from 'vue';
import Vuex from 'vuex';
import config from '@/config/config.js';
Vue.use(Vuex);
export default new Vuex.Store({
	state: {
		token: uni.getStorageSync('token') || '', // 从本地存储中获取 token
		user: null, // 用户信息
		friends: [], // 好友列表
		groups: [],
		lastMessages: {}, // 新增状态,用于存储最后一条消息
		hasMoreFriends: true // 新增状态用于跟踪是否有更多好友
	},
	mutations: {
		SET_TOKEN(state, token) {
			state.token = token;
			uni.setStorageSync('token', token); // 保存到本地存储
		},
		CLEAR_TOKEN(state) {
			state.token = '';
			uni.removeStorageSync('token'); // 从本地存储中删除
		},
		SET_USER(state, user) {
			state.user = user;
		},
		SET_FRIENDS(state, {
			friends,
			hasMoreFriends
		}) {
			state.friends = friends;
			state.hasMoreFriends = hasMoreFriends;
		},
		SET_GROUPS(state, groups) {
			state.groups = groups;
		},

		SET_LAST_MESSAGE(state, {
			id,
			message
		}) {
			Vue.set(state.lastMessages, id, message); // 动态设置最后一条消息
		}
	},
	actions: {
		async fetchGroups({
			commit
		}) {
			const token = uni.getStorageSync('token');
			const [error, response] = await uni.request({
				url: `${config.apiBaseUrl}/groups`,
				method: 'GET',
				header: {
					'Authorization': `Bearer ${token}`
				}
			});
			if (!error && response.data.code == 0) {
				commit('SET_GROUPS', response.data.data);
			}
			logoutpub(response, commit);
		},
		async createGroup({
			state
		}, {
			name,
			description,
			avatar_url
		}) {
			try {
				const token = uni.getStorageSync('token');
				const [error, response] = await uni.request({
					url: `${config.apiBaseUrl}/groups`,
					method: 'POST',
					header: {
						'Authorization': `Bearer ${token}`,
						'Content-Type': 'application/json'
					},
					data: {
						name,
						description,
						avatar_url
					}
				});
				logoutpub(response, commit);
				return response.data;
			} catch (error) {
				throw error;
			}
		},
		async login({
			commit
		}, {
			username,
			password
		}) {
			try {
				const [error, response] = await uni.request({
					url: `${config.apiBaseUrl}/login`,
					method: 'POST',
					data: {
						username,
						password
					}
				});
				if (error) {
					throw new Error(`Request failed with error: ${error}`);
				}
				response.data = response.data.data;
				if (response.data.token) {
					commit('SET_TOKEN', response.data.token);
					uni.redirectTo({
						url: '/pages/index/friends'
					});
				} else {
					throw new Error('Invalid credentials');
				}
			} catch (error) {
				console.error('Login error:', error);
				throw error;
			}
		},
		async fetchUser({
			commit
		}) {
			const token = uni.getStorageSync('token');
			if (!token) return;

			try {
				const [error, response] = await uni.request({
					url: `${config.apiBaseUrl}/user`,
					method: 'GET',
					header: {
						'Authorization': `Bearer ${token}`
					}
				});
				logoutpub(response, commit);

				if (error) {
					throw new Error(`Request failed with error: ${error}`);
				}

				if (response.statusCode === 200) {
					const userData = response.data.data || response.data;
					commit('SET_USER', userData);
				} else {
					throw new Error('Failed to fetch user data');
				}
			} catch (error) {
				console.error('Failed to fetch user:', error);
			}
		},
		async fetchFriends({
			commit
		}, {
			page = 1,
			perPage = 20
		}) {
			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}`
					},
					data: {
						page,
						perPage
					}
				});
				
				console.log("friends",response)
				
				logoutpub(response, commit);
				if (error) {
					throw new Error(`Request failed with error: ${error}`);
				}
				if (response.data) {
					commit('SET_FRIENDS', {
						friends: response.data.data,
						hasMoreFriends: response.data.hasMoreFriends
					});
				}
			} catch (error) {
				console.error(error);
			}
		},
		async handleNewMessage({
			commit
		}, {
			id,
			message
		}) {
			try {
				// 假设这是接收消息的逻辑
				commit('SET_LAST_MESSAGE', {
					id,
					message
				});
			} catch (error) {
				console.error('Failed to handle new message:', error);
			}
		},
		logout({
			commit
		}) {
			commit('CLEAR_TOKEN');
			commit('SET_USER', null);
			commit('SET_FRIENDS', {
				friends: [],
				hasMoreFriends: true
			});
			uni.redirectTo({
				url: '/pages/index/login' // 跳转到登录页面
			});
		}
	}
});

// Helper function for handling token expiration
function logoutpub(response, commit) {
	if (response.data && response.data.code === -1 && response.data.message === 'expire') {
		commit('CLEAR_TOKEN');
		commit('SET_USER', null);
		commit('SET_FRIENDS', {
			friends: [],
			hasMoreFriends: true
		});
		uni.redirectTo({
			url: '/pages/index/login' // 跳转到登录页面
		});
	}
}

在config创建config.js:

const config = {
  apiBaseUrl: 'http://localhost:3000'
};
export default config;

用户登陆后进入到friends页。

在这里插入图片描述
界面代码为:

<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">
        <text>您还没有添加任何好友。</text>
        <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  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="188"
            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>
    <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:'',
	  friendlist:[]
    };
  },

  computed: {
    ...mapState(['friends', 'token', 'lastMessages']),
    isLoggedIn() {
      return !!this.token;
    },
  },
  methods: {
    ...mapActions(['fetchFriends']),
 async getmsg() {
      this.socket.on('message', (msg) => {
        this.friends.forEach((friend, index) => {
          if (friend.id == msg.group_name) {
            this.$set(this.friends, index, { ...friend, message: msg.content,type:msg.type });
          }
        });
    
      });
    },
	
	
  async loadFriends() {
    if (!this.isLoggedIn) return;
    this.loading = true;
    try {
      await this.fetchFriends({ page: this.page, perPage: this.perPage });
      this.page++;
    } catch (error) {
      console.error('Failed to load friends:', error);
	  this.loading = false;
    } finally {
      this.loading = false;
    }
  },
    toChat(item) {
		console.log(":::::item.type::::",item.type)
		if(item.type=='user'){
			uni.navigateTo({
			  url: '/pages/index/chat?id=' + item.id + '&type=' + item.type + '&tid='+item.group_friend_id
			});
		}else{
			uni.navigateTo({
			  url: '/pages/index/chat?id=' + "g_"+item.group_friend_id + '&type=' + item.type + '&tid='+item.group_friend_id
			});
		}
    
    },
    loadMoreFriends() {
      this.page++;
      this.loadFriends();
    },
    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();
    },
	  getLastMessage(id) {
		console.log('Getting last message for ID:', id);
		return this.lastMessages && this.lastMessages[id] ? this.lastMessages[id] : '暂无信息';
	  }

  },
  mounted() {
    if (this.isLoggedIn) {
      this.loadFriends();
    }
	 this.socket = io('http://127.0.0.1:3000');
	    this.socket.on('connect', () => {
	      console.log('Socket connected:', this.socket.id);
	    });
	
	    this.socket.on('disconnect', () => {
	      console.log('Socket disconnected');
	    });
	    this.getmsg();
  }
};
</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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1975544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探秘北京崇文门中医医院卫景沛医生:为何深受患者信赖?

卫景沛是北京崇文门中医医院特聘专家&#xff0c;深受患者信赖&#xff01; 北京崇文门中医医院卫景沛主任毕业于兰州大学医学院&#xff0c;拥有医学硕士学位。他的硕士导师是天坛医院的王拥军教授&#xff0c;主要研究方向为脑血管病及脑血管病介入治疗。 为了提升自己在缺血…

对称加密:数据安全的保障

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Qt对象树的介绍

目录 创建项目&#xff08;此处我就不多介绍了&#xff09; 按钮 对象树 创建项目&#xff08;此处我就不多介绍了&#xff09; QMainWidow带菜单栏的 QWidget空白的 QDialog对话框 创建功能时注意&#xff1a; 项目工程名称一般不要有标点&#xff0c;不要带中文 按钮 /…

计算机基础(Windows 10+Office 2016)教程 —— 第8章 多媒体技术及应用

多媒体技术及应用 8.1 多媒体技术的概述8.1.1 多媒体技术的定义和特点8.1.2  多媒体的关键技术8.1.3 多媒体技术的发展趋势8.1.4 多媒体文件格式的转换8.1.5 多媒体技术的应用 8.2 多媒体计算机系统的构成8.2.1 多媒体计算机系统的硬件系统8.2.2 多媒体计算机系统的软件系统…

Python教程(十一):单元测试与异常捕获

目录 专栏列表前言一、Python中的测试1.1 单元测试1.1.1 定义测试类1.2.1 安装 pytest1.2.2 编写测试1.2.3 运行测试 二、Python中的异常捕获2.1 常规代码2.2 异常基础 三、抛出异常&#xff08;异常传播&#xff09;四、 自定义异常 专栏列表 Python教程&#xff08;一&#…

赛蓝企业管理系统 AuthToken/Index 身份认证绕过漏洞复现

0x01 产品简介 赛蓝企业管理系统是一款为企业提供全面管理解决方案的软件系统&#xff0c;它能够帮助企业实现精细化管理&#xff0c;提高效率&#xff0c;降低成本。系统集成了多种管理功能&#xff0c;包括但不限于项目管理、财务管理、采购管理、销售管理以及报表分析等&am…

【WPF开发】如何将工程打包成单独的EXE安装包

一、安装NSIS与HM NIS Edit 1、下载和安装NSIS NSIS官网 2、下载和安装HM NIS Edit HM NIS Edit官网 点击下载后等待几秒&#xff0c;就会弹出下载提示 双击下载的安装包&#xff0c;点击“OK” 点击“下一步” 点击“我接受” 更改路径后&#xff0c;点击安装即可 二、打包软…

SpringSecurity-1(认证和授权+SpringSecurity入门案例+自定义认证+数据库认证)

SpringSecurity 1 初识权限管理1.1 权限管理的概念1.2 权限管理的三个对象1.3 什么是SpringSecurity 2 SpringSecurity第一个入门程序2.1 SpringSecurity需要的依赖2.2 创建web工程2.2.1 使用maven构建web项目2.2.2 配置web.xml2.2.3 创建springSecurity.xml2.2.4 加载springSe…

【leetcode详解】寻找两个正序数组的中位数:最简单的【困难】题?

简评&#xff1a; 可以说&#xff0c;要做出来这道题&#xff0c;实际上是非常简单的 //这也是笔者目前唯一解出来的唯一一道【困难】题哈哈哈哈 思路解析&#xff1a; 将两个向量合并 class Solution { public:double findMedianSortedArrays(vector<int>& nums1…

c# MetroForm 和 IntPtr unsafe

一、NuGet安装框架 修改代码 效果&#xff1a; 结果&#xff1a; TopLevel与TopMost属性 frm.TopLevel false; //Form.TopLevel 获取或设置一个值&#xff0c;该值指示是否将窗体显示为顶级窗口。frm.TopMost false; //Form.TopMost 获取或设置一个值&#xff0c;指示该窗体…

嵌入式人工智能(43-基于树莓派4B的刷卡模块射频识别RFID-RC522)

1、RFID 射频识别&#xff08;RFID&#xff0c;Radio Frequency Identification&#xff09;是一种无线通信技术&#xff0c;用于自动识别和追踪标签上的信息。这项技术基于射频信号的传输和接收&#xff0c;通过将标签上的数据存储在特定的芯片中&#xff0c;实现物体的识别和…

上市公司绿色信息披露质量评分数据(2008-2023年)

数据来源&#xff1a;基础数据来源于上市公司年报/社会责任报告/环境报告以及ZJ会及统计局 时间跨度&#xff1a;2008-2023年 数据范围&#xff1a;企业及行业层面 数据指标&#xff1a; 按照是否货币化分类企业对于环境信息的披露:对于货币化的信息&#xff0c;定量和定性…

共襄恰青赛马节盛事 共享农业产业园成果

恰青赛马节是那曲一年一度的草原盛事&#xff0c;是藏北规模盛大的传统节日&#xff0c;承载着那曲悠久的文化底蕴&#xff0c;体现了藏北各族群众丰富的传统习俗&#xff0c;更是深受民众欢迎。今年的赛马节上&#xff0c;色尼区国家现代农业产业园紧抓机遇&#xff0c;设置农…

Android设备发送蓝牙文件到电脑笔记本失败解决

Android设备发送蓝牙文件到电脑笔记本失败解决 文章目录 Android设备发送蓝牙文件到电脑笔记本失败解决一、前言二、解决1、比较旧的电脑2、大部分新的电脑 三、其他1、发送蓝牙文件到Window电脑端小结2、可传输的蓝牙文件的文件类型 一、前言 普通手机之间蓝牙配对后&#xf…

软件测试开发

软件测试的职业发展 起点&#xff1a;功能测试 走管理 业务专家行业业务专家行业业务发展专家 走技术 测试开发资深测试开发测试架构师/全栈测试工程师 软件开发模型 瀑布模型 V模型和W模型 W模型和V模型都把软件的开发视为需求&#xff0c;设计&#xff0c;编码&#x…

SolarMarker 正在使用水坑攻击与伪造的 Chrome 浏览器更新进行攻击

在过去的三个月里&#xff0c;eSentire 的安全研究团队发现信息窃密恶意软件 SolarMarker 都没有发动攻击&#xff0c;却在最近忽然重返舞台。此前&#xff0c;SolarMarker 的运营者使用 SEO 投毒或者垃圾邮件来引诱受害者&#xff0c;受害者试图下载一些文档的免费模板&#x…

非对称加密:数据安全的双重保障

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

每日OJ_牛客HJ73 计算日期到天数转换

目录 牛客HJ73 计算日期到天数转换 解析代码 牛客HJ73 计算日期到天数转换 计算日期到天数转换_牛客题霸_牛客网 解析代码 用一个数组存放每月的累积天数输入的日期天数 当月的天数 当月之前的累积天数&#xff0c;如果包含二月&#xff0c;再去判断是否为闰年&#xff0c;…

听专家的,不如听国家的,网络安全究竟值不值得报?

考学选专业&#xff0c;或者跳槽选行业的&#xff0c;看这篇&#xff01; 如果你什么都不懂&#xff0c;家里也没有矿&#xff0c;那就紧跟国家大事和地方政策。 关于网络安全专业究竟是否值得报考? 要知道“二十大”、“十四五”等大会一直在提一个词叫做“数字中国建设”…

精通推荐算法19:特征交叉之DeepFM -- 异构模型Wide侧引入FM

1 引言 Wide & Deep的提出&#xff0c;使推荐模型同时具备记忆和泛化能力。通过融合低阶和高阶特征交叉&#xff0c;开启了推荐算法异构模型的风潮。后续越来越多的模型&#xff0c;在其基础上进一步优化&#xff0c;并取得了不错的效果。DeepFM就是其中一个很经典的模型&…