# 搭建聊天室(前端 vue3 + pinia + axios + elementplus)02 pinia 和 axios 集成
# 一、集成 Pinia
Pinia | The intuitive store for Vue.js (vuejs.org)
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
如果使用 Pinia 你将获得
Devtools
支持- 追踪
actions
、mutations
的时间线- 在组件中展示它们所用到的
Store
- 让调试更容易的
Time travel
热更新
不必重载页面
即可修改 Store- 开发时可保持当前的 State
- 插件:可通过插件扩展
Pinia
功能- 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
- 支持服务端渲染
基本用法可以参考这篇文章 vue 中如何优雅高效的使用 pinia - 知乎 (zhihu.com)
我就直接讲我们这个项目怎么集成了。
我使用了 Pinia 的持久化存储插件 pinia-plugin-persist
,它提供了简便的使用方式以及灵活的配置方式。
# 安装 | |
pnpm i pinia-plugin-persist |
在 @/store/index.js 中写入
import { createPinia } from "pinia"; | |
import piniaPersist from "pinia-plugin-persist"; | |
const store = createPinia() | |
// 注册插件 | |
store.use(piniaPersist) | |
export default store |
main.js 中引入
import { createApp } from "vue"; | |
import "@/styles/reset.scss"; // global css | |
import App from "@/App.vue"; | |
import router from "@/router"; | |
import ElementPlus from "element-plus"; | |
import zhCn from "element-plus/dist/locale/zh-cn.mjs"; | |
import store from "@/store"; | |
const app = createApp(App); | |
app.use(router); | |
app.use(ElementPlus, { | |
locale: zhCn, | |
size: "normal", | |
}); | |
app.use(store); | |
app.mount("#app"); |
以上就引入完毕了。
# 二、封装集成 Axios
我们最开始已经安装过 axios,所以现在只要封装就好。
新建.env.development 和.env.production
###
# @Author: Anixuil
# @Date: 2024-02-28 10:45:40
# @LastEditors: Anixuil
# @LastEditTime: 2024-02-28 13:44:22
# @Description: 开发环境配置
###
CHAT_ENV = 'development'
# base api
CHAT_BASE_API = '/anixuil/chatserver'
CHAT_BASE_URL = 'http://localhost:8080'
CHAT_BASE_API_REWRITE = '/anixuil/chatserver'
###
# @Author: Anixuil
# @Date: 2024-02-28 10:46:29
# @LastEditors: Anixuil
# @LastEditTime: 2024-02-28 13:56:15
# @Description: 生产环境配置
###
CHAT_ENV = 'production'
# base api
CHAT_BASE_API = '/anixuil/chatserver'
CHAT_BASE_URL = 'http://121.37.4.76:8080'
新建 @/api/request.js
import axios from "axios"; | |
// 创建 axios 实例 | |
const service = axios.create({ | |
baseURL: import.meta.env.CHAT_BASE_URL + import.meta.env.CHAT_BASE_API, //api 的 base_url | |
timeout: 36000, // 请求超时时间 | |
}); | |
// 请求拦截器 | |
service.interceptors.request.use( | |
(config) => { | |
// 在发送请求之前做些什么 | |
return config; | |
}, | |
(error) => { | |
// 对请求错误做些什么 | |
return Promise.reject(error); | |
} | |
); | |
// 响应拦截器 | |
service.interceptors.response.use( | |
(response) => { | |
// 对响应数据进行处理 | |
let res = response.data; | |
if (response.status === 200) { | |
if (res?.code) { | |
if (res.code === 0) { | |
return res; | |
} else { | |
return Promise.reject(res); | |
} | |
} else { | |
return res; | |
} | |
} else { | |
return Promise.reject(res); | |
} | |
}, | |
(error) => { | |
// 对响应错误做点什么 | |
return Promise.reject(error); | |
} | |
); | |
export default service; |
@/api/user/index.js
import service from '@/api/request' | |
export function login(data) { | |
return service({ | |
url: '/user/login', | |
method: 'post', | |
data | |
}) | |
} |
新建一个 login.vue 写一个表单测试一下功能
<template>
<div class="login-wrapper">
<div class="login-container">
<h2 class="login-title">登录</h2>
<div class="login-form">
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
label-width="80px"
>
<el-form-item label="邮箱" prop="email">
<el-input v-model="loginForm.email"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="loginForm.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin">登录</el-button>
<el-button @click="showRegisterForm = true">注册</el-button>
</el-form-item>
</el-form>
</div>
<div class="register-form" v-if="showRegisterForm">
<el-form
ref="registerForm"
:model="registerForm"
:rules="registerRules"
label-width="80px"
>
<el-form-item label="邮箱" prop="email">
<el-input v-model="registerForm.email"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
type="password"
v-model="registerForm.password"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleRegister">注册</el-button>
<el-button @click="showRegisterForm = false">返回登录</el-button>
</el-form-item>
</el-form>
</div>
<div class="privacy-policy">
<el-checkbox v-model="agreePrivacy"
>我已阅读并同意 用户隐私协议</el-checkbox
>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
import { login } from "@/api/user";
import useGoto from "@/hooks/useGoto";
const { goto } = useGoto();
const loginForm = reactive({
email: "",
password: "",
});
const registerForm = reactive({
email: "",
password: "",
});
const loginRules = reactive({
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{
type: "email",
message: "请输入正确的邮箱格式",
trigger: ["blur", "change"],
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
});
const registerRules = reactive({
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{
type: "email",
message: "请输入正确的邮箱格式",
trigger: ["blur", "change"],
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
});
const showRegisterForm = ref(false);
const agreePrivacy = ref(false);
const loginFormRef = ref(null);
const handleLogin = () => {
loginFormRef.value.validate(async (valid) => {
if (valid) {
try {
let params = {
userEmail: loginForm.email,
userPassword: loginForm.password,
};
let res = await login(params);
ElNotification({
title: "登录成功",
type: "success",
});
goto("index");
} catch (err) {
console.log(err);
ElNotification({
title: "登录失败, 请检查输入信息",
type: "error",
});
}
} else {
ElNotification({
title: "登录失败, 请检查输入信息",
type: "error",
});
}
});
};
const handleRegister = () => {
// Perform register logic here
};
</script>
<style lang="scss" scoped>
.login-wrapper {
background: linear-gradient(to right, #ff9a9e, #fad0c4);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-container {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
width: 400px;
}
.login-title {
text-align: center;
font-size: 24px;
margin-bottom: 20px;
}
.login-form {
margin-bottom: 20px;
}
.register-form {
display: none;
}
.privacy-policy {
text-align: center;
margin-top: 20px;
}
</style>
# 三、Pinia+Axios 结合实现登录 token 存储和请求携带 token
后端我已经搭好了基本框架,文章还未整理出来,还需耐心等待。
如图,我的后端接口是返回了当前登录用户的 token 的,都学 vue3 了,大家应该对登录注册的业务流程非常熟悉了,我就不做多赘述,直接上干货。
我们在 @/store/modules/user.js 写入
/* | |
* @Author: Anixuil | |
* @Date: 2024-02-28 15:58:08 | |
* @LastEditors: Anixuil | |
* @LastEditTime: 2024-02-28 16:37:38 | |
* @Description: pinia user store | |
*/ | |
import { defineStore } from "pinia"; | |
export const useUserStore = defineStore("user", { | |
state: () => ({ | |
token: "", | |
}), | |
getters: { | |
getToken() { | |
return this.token; | |
}, | |
}, | |
actions: { | |
// 保存 token | |
setToken(token) { | |
this.token = token; | |
}, | |
}, | |
// 持久化存储配置 | |
persist: { | |
enabled: true, | |
strategies: [{ | |
// 持久化存储的 key | |
key: 'userInfo', | |
// 持久化存储的路径 | |
storage: localStorage, | |
//state 的字段 | |
paths: ['token'] | |
}] | |
}, | |
}); |
修改 @/store/index.js
import { createPinia } from "pinia"; | |
import piniaPersist from "pinia-plugin-persist"; | |
const store = createPinia() | |
// 注册插件 | |
store.use(piniaPersist) | |
export default store | |
export * from './modules/user' |
然后在 login.vue 中使用
<script setup>
import { reactive, ref } from "vue";
import { login } from "@/api/user";
import useGoto from "@/hooks/useGoto";
import { useUserStore } from "@/store/index";
// 引入自定义的useGoto hook
const { goto } = useGoto();
// 引入用户store
const useUser = useUserStore();
const loginForm = reactive({
email: "",
password: "",
});
const registerForm = reactive({
email: "",
password: "",
});
const loginRules = reactive({
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{
type: "email",
message: "请输入正确的邮箱格式",
trigger: ["blur", "change"],
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
});
const registerRules = reactive({
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{
type: "email",
message: "请输入正确的邮箱格式",
trigger: ["blur", "change"],
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
});
const showRegisterForm = ref(false);
const agreePrivacy = ref(false);
const loginFormRef = ref(null);
const handleLogin = () => {
loginFormRef.value.validate(async (valid) => {
if (valid) {
try {
let params = {
userEmail: loginForm.email,
userPassword: loginForm.password,
};
let res = await login(params);
if (res?.data?.token) {
useUser.setToken(res.data.token);
ElNotification({
title: "登录成功",
type: "success",
});
goto("index");
} else {
ElNotification({
title: "登录失败, 请检查输入信息",
type: "error",
});
}
} catch (err) {
console.log(err);
ElNotification({
title: "登录失败, 请检查输入信息",
type: "error",
});
}
} else {
ElNotification({
title: "登录失败, 请检查输入信息",
type: "error",
});
}
});
};
const handleRegister = () => {
// Perform register logic here
};
</script>
再在完成一些页面之后,做一个路由守卫就闭环了。