socket 客户端——登录、注册
新增登录、注册、上传头像接口
src/api/user/index.js
/**
* @author Wuner
* @date 2022/9/27 14:08
* @description user 用户模块
*/
import { post } from '@/utils/http';
/**
* 登录
* @param data
* @param data.username => 用户名
* @param data.password => 密码
* @returns
*/
const url = '/socket-chat-back/user';
export const login = (data) => {
return post(`${url}/login`, data);
};
/**
* 登录
* @param data
* @param data.username => 用户名
* @param data.password => 密码
* @param data.avatar => 头像路径
* @returns
*/
export const register = (data) => {
return post(`${url}/register`, data);
};
- post 请求新增 headers
src/utils/http.js
const post = (url, data, headers) => {
return axios({
method: 'post',
url,
data,
headers,
});
};
src/api/upload/index.js
import { post } from '@/utils/http';
const url = '/socket-chat-back/upload';
/**
* 上传头像
* @param file 图片文件
*/
export const uploadAvatar = (file) => {
const formData = new FormData();
formData.append('img', file);
return post(`${url}/avatar`, formData, {
'Content-Type': 'multipart/form-data',
});
};
vuex 新增登录信息获取及缓存
/**
* @author Wuner
* @date 2020/12/9 18:00
* @description 用户模块
*/
import Session from '@/utils/session';
import { login } from '@/api/api/user';
const storeState = Session.get('storeState');
const state = (storeState && storeState.user) || {
// 用户信息
userinfo: {
username: '',
nickname: '',
avatar: '',
},
};
const getters = {};
const mutations = {
/**
* 设置用户信息
* @param state
* @param data
*/
setUserinfo(state, data) {
state.userinfo = data;
},
};
const actions = {
/**
* 登录
* @param ***mit
* @param user
* @returns {Promise<void>}
*/
login({ ***mit }, user) {
return new Promise(async (resolve, reject) => {
try {
const data = await login(user);
***mit('setUserinfo', data.userinfo);
resolve(data);
} catch (e) {
console.log(e);
reject(e);
}
});
},
};
export default { namespaced: true, state, getters, mutations, actions };
新增登录、注册页面
- 登录页面
src/view/login/index.vue
<template>
<div class="login">
<img class="login-logo" src="../../assets/logo.png" alt="" />
<div class="login-input">
<van-field
v-model="username"
left-icon="manager"
clearable
placeholder="请输入用户名"
/>
<van-field
v-model="password"
left-icon="lock"
type="password"
clearable
placeholder="请输入密码"
/>
</div>
<div class="login-remember">
<van-checkbox v-model="checked">记住我的账号和密码</van-checkbox>
<span @click="onRegister">新用户</span>
</div>
<div class="login-btn" @click="onLogin">登录</div>
</div>
</template>
<script>
import { remove, set, get } from '@/utils/***mon-utils';
import { mapActions } from 'vuex';
import { createSocket } from '@/utils/socket';
export default {
data() {
return {
username: '',
password: '',
// 是否记住密码
checked: false,
};
},
methods: {
...mapActions('user', ['login']),
/**
* 登录
*/
onLogin() {
const { username, password, checked } = this;
if (!username) {
this.$toast('请输入用户名');
return;
}
if (!password) {
this.$toast('请输入密码');
return;
}
this.login({ username, password })
.then(() => {
// 记住密码时,缓存用户账号信息
if (checked) {
set('checked', checked);
set('password', password);
set('username', username);
} else {
remove('password');
remove('checked');
remove('username');
}
// 创建 socket 服务
createSocket(username);
this.$router.push('/');
})
.catch((e) => {
this.$toast(e);
});
},
/**
* 跳转注册页面
*/
onRegister() {
this.$router.push({ name: 'register' });
},
},
created() {
// 记住密码时,获取缓存的账号信息进行赋值
if (get('checked')) {
this.username = get('username');
this.checked = get('checked');
this.password = get('password');
}
},
};
</script>
<style lang="less">
.input {
.van-field__left-icon {
.van-icon {
color: #999;
}
}
.checkbox {
font-size: 14px;
margin-top: 20px;
margin-left: 10px;
.van-checkbox__icon {
width: 10px;
height: 10px;
line-height: 10px;
}
.van-icon {
font-size: 10px;
}
}
}
</style>
<style scoped lang="less">
.login {
padding: 0 16px;
&-logo {
width: 150px;
display: block;
margin: 50px auto;
}
&-input {
.van-cell {
border-bottom: 1px solid #999;
}
.van-icon {
color: #999;
}
.van-cell + .van-cell {
margin-top: 20px;
}
}
&-remember {
display: flex;
justify-content: space-between;
margin-top: 8px;
align-items: center;
span {
color: #0f88e4;
font-size: 14px;
}
}
&-btn {
margin: 30px auto;
height: 36px;
background-color: #0f88e4;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 14px;
border-radius: 6px;
}
}
</style>
- 注册页面
src/view/register/index.vue
<template>
<div class="register">
<img class="register-logo" src="../../assets/logo.png" alt="" />
<div class="register-input">
<p class="register-input-desc">上传头像</p>
<van-uploader
v-model="fileList"
:after-read="afterRead"
:before-read="beforeRead"
@delete="onDelete"
:max-count="1"
/>
<van-field
v-model="username"
left-icon="manager"
clearable
placeholder="请输入用户名"
/>
<van-field
v-model="nickname"
left-icon="manager"
clearable
placeholder="请输入昵称"
/>
<van-field
v-model="password"
left-icon="lock"
type="password"
clearable
placeholder="请输入密码"
/>
<van-field
v-model="password1"
left-icon="lock"
type="password"
clearable
placeholder="请再次输入密码"
/>
</div>
<div class="register-btn" @click="onRegister">注册</div>
</div>
</template>
<script>
import { uploadAvatar } from '@/api/api/upload';
import { register } from '@/api/api/user';
export default {
data() {
return {
nickname: '',
username: '',
password: '',
password1: '',
fileList: [],
avatar: '',
};
},
methods: {
/**
* 注册
*/
onRegister() {
const { nickname, username, password, password1, avatar } = this;
if (!avatar) {
this.$toast('请上传头像');
return;
}
if (!username) {
this.$toast('请输入用户名');
return;
}
if (!nickname) {
this.$toast('请输入昵称');
return;
}
if (!password) {
this.$toast('请输入密码');
return;
}
if (!password1) {
this.$toast('请再次输入密码');
return;
}
if (password !== password1) {
this.$toast('两次输入的密码不一致');
return;
}
register({ nickname, username, password, avatar })
.then(() => {
this.$toast('注册成功');
this.$router.replace({ name: 'login' });
})
.catch((e) => {
this.$toast(e);
});
},
/**
* 文件读取前的回调函数,返回 false 可终止文件读取,
* @param file
* @returns {boolean}
*/
beforeRead(file) {
// 判断文件是否是 jpg 和 png 格式
if (!['image/jpeg', 'image/png'].includes(file.type)) {
this.$toast('请上传 jpg 或 png 格式图片');
return false;
}
return true;
},
/**
* 文件读取完成后的回调函数
* @param file
*/
afterRead(file) {
file.status = 'uploading';
file.message = '上传中...';
uploadAvatar(file.file)
.then((data) => {
file.status = 'done';
this.avatar = data.imgPath;
})
.catch(() => {
file.status = 'failed';
file.message = '上传失败';
});
},
/**
* 删除图片时触发
*/
onDelete() {
this.avatar = '';
},
},
};
</script>
<style lang="less">
.input {
.van-field__left-icon {
.van-icon {
color: #999;
}
}
.checkbox {
font-size: 14px;
margin-top: 20px;
margin-left: 10px;
.van-checkbox__icon {
width: 10px;
height: 10px;
line-height: 10px;
}
.van-icon {
font-size: 10px;
}
}
}
</style>
<style scoped lang="less">
.register {
&-logo {
width: 150px;
display: block;
margin: 50px auto;
}
&-input {
margin: 0 30px;
.van-cell {
border-bottom: 1px solid #999;
}
.van-icon {
color: #999;
}
.van-cell + .van-cell {
margin-top: 20px;
}
&-desc {
font-size: 12px;
color: #999999;
padding: 4px 0;
}
}
&-btn {
margin: 30px auto;
width: 90%;
height: 36px;
background-color: #0f88e4;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 14px;
border-radius: 6px;
}
}
</style>
- 我的页面增加用户信息展示
src/view/mine/index.vue
<template>
<div class="mine">
<div class="mine-header">
<img :src="userinfo.avatar" alt="" />
<div>
<p class="mine-header-nickname">{{ userinfo.nickname }}</p>
<p class="mine-header-username">用户名:{{ userinfo.username }}</p>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'mine',
data() {
return {};
},
***puted: {
...mapState('user', ['userinfo']),
},
};
</script>
<style scoped lang="less">
.mine {
min-height: 100vh;
background-color: #f5f5f5;
&-header {
display: flex;
padding: 16px;
background-color: white;
img {
width: 60px;
height: 60px;
border-radius: 4px;
}
div {
margin-left: 8px;
display: flex;
flex-wrap: wrap;
align-content: space-between;
p {
width: 100%;
}
}
&-nickname {
font-size: 20px;
font-weight: bold;
}
&-username {
font-size: 12px;
color: #666;
}
}
}
</style>
- 新增头部导航栏
- 在登录等页面不需要出现返回按钮
src/App.vue
<van-nav-bar
:title="$route.meta.title"
:left-arrow="!['message', 'friends', 'mine', 'login'].includes($route.name)"
@click-left="$router.back()"
/>
路由配置
src/router/index.js
{
path: '/login',
name: 'login',
***ponent: () =>
import(/*webpackChunkName: "index"*/ '@/view/login/index.vue'),
meta: {
title: '登录',
},
},
{
path: '/register',
name: 'register',
***ponent: () =>
import(/*webpackChunkName: "index"*/ '@/view/register/index.vue'),
meta: {
title: '注册',
},
},
去除假数据
main.js
- 之前写的假数据,我们直接干掉
router.beforeEach((to, from, next) => {
if (store.state.user.userinfo.username && !window.socket) {
createSocket(store.state.user.userinfo.username);
next();
} else {
next();
}
});
配置跨域请求
config/index.js
proxyTable: {
'/socket-chat-back/': {
target: 'http://127.0.0.1:4000', //测试环境
changeOrigin: true,
},
},