用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 分钟前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
m0_748239833 分钟前
前端bug调试
前端·bug
m0_748232925 分钟前
[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结
前端·搜索引擎
新中地GIS开发老师11 分钟前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
m0_7482495413 分钟前
前端:base64的作用
前端
html组态19 分钟前
web组态可视化编辑器
前端·物联网·编辑器·web组态·组态·组态软件
~央千澈~26 分钟前
如果你的网站是h5网站,如何将h5网站变成小程序-除开完整重做方法如何快速h5转小程序-h5网站转小程序的办法-优雅草央千澈
前端·apache
m0_7482398338 分钟前
基于web的音乐网站(Java+SpringBoot+Mysql)
java·前端·spring boot
时雨h43 分钟前
RuoYi-ue前端分离版部署流程
java·开发语言·前端
Cachel wood1 小时前
Django REST framework (DRF)中的api_view和APIView权限控制
javascript·vue.js·后端·python·ui·django·前端框架