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

界面如下:

c 复制代码
<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>

注册页:

c 复制代码
<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:

c 复制代码
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:

c 复制代码
const config = {
  apiBaseUrl: 'http://localhost:3000'
};
export default config;

用户登陆后进入到friends页。

界面代码为:

c 复制代码
<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>
相关推荐
咖啡の猫2 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲4 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
2501_915918415 小时前
iOS 应用上架全流程实践,从开发内测到正式发布的多工具组合方案
android·ios·小程序·https·uni-app·iphone·webview
朝阳5815 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路5 小时前
GeoTools 读取影像元数据
前端
ssshooter5 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友5 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry6 小时前
Jetpack Compose 中的状态
前端
dae bal7 小时前
关于RSA和AES加密
前端·vue.js
柳杉7 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化