# 搭建聊天室(前端 vue3 + pinia + axios + elementplus)02 pinia 和 axios 集成

# 一、集成 Pinia

Pinia | The intuitive store for Vue.js (vuejs.org)

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。

如果使用 Pinia 你将获得

  • Devtools 支持
  • 追踪 actionsmutations 的时间线
  • 在组件中展示它们所用到的 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

后端我已经搭好了基本框架,文章还未整理出来,还需耐心等待。

image-20240228154742844

如图,我的后端接口是返回了当前登录用户的 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>

image-20240228164310586

再在完成一些页面之后,做一个路由守卫就闭环了。