当前位置:   article > 正文

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

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

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

<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

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

<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

在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' // 跳转到登录页面
		});
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233

在config创建config.js:

const config = {
  apiBaseUrl: 'http://localhost:3000'
};
export default config;
  • 1
  • 2
  • 3
  • 4

用户登陆后进入到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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/931962
推荐阅读
相关标签
  

闽ICP备14008679号