目录
一.接口地址
二.apipost 接口测试,能否接通
三.安装axiox
1.下载安装依赖
2.新建src/utils/request.ts文件
2.1解释:后续后端真实接口需要替换baseURL,目前没有使用配置文件,后续更换
3.新建src/utils/storage.ts文件
4.新建src/utils/formDataFormat.ts文件,
5.修改vite.config.ts文件,添加代理转发
5.1解释:后面后端真实接口直接替换即可
6.新建src/api/login 文件夹
7.目录结构
二.测试使用
1.修改login.vue文件
2.测试效果
三.一些说明修改
1.stores/interface/index.ts修改
2.stores/states.ts修改
一.接口地址
fastmock接口项目地址:
https://www.fastmock.site/mock/b9536a1dea3fe4daeec18bc365e14a18/api
登录接口 ,完整引用路径(apipost测试时使用):
https://www.fastmock.site/mock/b9536a1dea3fe4daeec18bc365e14a18/api/login
相对路径,后面在api/login/index.ts里使用
/login
二.apipost 接口测试,能否接通
进入软件,新建一个接口即可,测试如下
登录信息,注意是body的raw的json格式
{
"username":"admin",
"password":"admin"
}
三.安装axiox
1.下载安装依赖
npm install axios
npm install @types/axios --save-dev
用来使用Cookie
npm install js-cookie
npm install @types/js-cookie --save-dev
2.新建src/utils/request.ts文件
根据需求,相应的更改
2.1解释:后续后端真实接口需要替换baseURL,目前没有使用配置文件,后续更换
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Session } from '@/utils/storage';
// 配置新建一个 axios 实例
const service = axios.create({
baseURL: 'https://www.fastmock.site/mock/b9536a1dea3fe4daeec18bc365e14a18/api',
timeout: 50000,
headers: { 'Content-Type': 'application/json' },
});
// 添加请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
if (Session.get('token')) {
(<any>config.headers).common['token'] = `${Session.get('token')}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
const res = response.data;
if (res.code && res.code !== '2000') {
// `token` 过期或者账号已在别处登录
if (res.code === 401 || res.code === "4001") {
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
.then(() => {})
.catch(() => {});
}
return response.data;
// return Promise.reject(service.interceptors.response);
} else {
return response.data;
}
},
(error) => {
// 对响应错误做点什么
if (error.message.indexOf('timeout') != -1) {
ElMessage.error('网络超时');
} else if (error.message == 'Network Error') {
ElMessage.error('网络连接错误');
} else {
if (error.response.data) ElMessage.error(error.response.statusText);
else ElMessage.error('接口路径找不到');
}
return Promise.reject(error);
}
);
// 导出 axios 实例
export default service;
3.新建src/utils/storage.ts文件
持久化处理
import Cookies from 'js-cookie';
/**
* window.localStorage 浏览器永久缓存
* @method set 设置永久缓存
* @method get 获取永久缓存
* @method remove 移除永久缓存
* @method clear 移除全部永久缓存
*/
export const Local = {
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
let json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
},
};
/**
* window.sessionStorage 浏览器临时缓存
* @method set 设置临时缓存
* @method get 获取临时缓存
* @method remove 移除临时缓存
* @method clear 移除全部临时缓存
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
if (key === 'token') return Cookies.set(key, val);
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
if (key === 'token') return Cookies.get(key);
let json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
if (key === 'token') return Cookies.remove(key);
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
Cookies.remove('token');
window.sessionStorage.clear();
},
};
4.新建src/utils/formDataFormat.ts文件,
处理访问接口时传递参数格式
export function objectToFormData(obj: Record<string, any>, form?: FormData, namespace?: string): FormData {
const fd = form || new FormData();
let formKey: string;
for (const property in obj) {
if (obj.hasOwnProperty(property)) {
const key = Array.isArray(obj) ? '[]' : `[${property}]`;
if (namespace) {
formKey = `${namespace}${key}`;
} else {
formKey = property;
}
// 如果属性是对象,但不是 File 对象,则递归调用 objectToFormData 处理嵌套对象
if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
objectToFormData(obj[property], fd, formKey);
} else {
// 如果属性值是字符串或 File 对象,直接添加到 FormData
fd.append(formKey, obj[property]);
}
}
}
return fd;
}
5.修改vite.config.ts文件,添加代理转发
5.1解释:后面后端真实接口直接替换即可
添加部分
server: {
host: '0.0.0.0',
port: 5000,
open: true,
proxy: {
'/api': {
target: 'https://www.fastmock.site/mock/b9536a1dea3fe4daeec18bc365e14a18/api',
ws: true,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
目前为止的完整文件内容
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// 以下为按需导入,自动引入手动导入element plus
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
//手动导入element plus 时
// import ElementPlus from 'unplugin-element-plus/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// ElementPlus({
// } ),//手动导入element plus 时
// 以下为按需导入,自动引入手动导入element plus
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
extensions: ['.vue', '.ts'],
alias: {
"@": path.resolve(__dirname, "src"),
}
},
server: {
host: '0.0.0.0',
port: 8888,
open: true,
proxy: {
'/api': {
target: 'https://www.fastmock.site/mock/b9536a1dea3fe4daeec18bc365e14a18/api',
ws: true,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
})
6.新建src/api/login 文件夹
新建src/api/login/index.ts文件以及src/api/login/types.ts文件
// login/index.ts
import request from '@/utils/request';
import { objectToFormData } from "@/utils/formDataFormat";// post请求参数是form-data时,使用此方法处理数据
import { LoginState } from './types';
/**
* 登录api接口集合
* @method login用户登录
* @method logout用户退出登录
*/
export function UseLoginApi() {
return {
login: (params: LoginState) => {
return request({
url: '/login',
method: 'post',
data: objectToFormData(params),
});
},
// logout: (params: object) => {
// return request({
// url: '/logout',
// method: 'post',
// data: objectToFormData(params),
// });
// },
};
}
// login/types.ts
// 登录参数类型
export interface LoginState{
username:string;
password:string
}
// 拿到的用户数据类型定义
export interface UserInfoState{
user:string;
ID:string;
age:string | number ;
sex:string
}
7.目录结构
二.测试使用
1.修改login.vue文件
添加内容
import { UseLoginApi } from "@/api/login/index"
import { LoginState } from "@/api/login/types";
const useLoginApi = UseLoginApi()
const login = () => {
let data: LoginState = {
username: "admin",
password: "admin"
}
useLoginApi.login(data)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
}
完整login.vue文件内容
<template>部分
<template>
<div class="loginForm">
<div class="rloginTitle">登录</div>
<!-- 各个输入框 -->
<el-form :model="state.formData" :rules="formRules" ref="formRef" label-width="50px">
<el-form-item prop="username">
<div class="formInput">
<el-input v-model="state.formData.username" placeholder="username" clearable autocomplete="off"
prefix-icon="User"></el-input>
</div>
</el-form-item>
<el-form-item prop="password">
<div class="formInput">
<el-input v-model="state.formData.password" placeholder="password" type="password" show-password
autocomplete="off" prefix-icon="Lock"></el-input>
</div>
</el-form-item>
<!-- 验证码 -->
<el-form-item>
<div class="formInput codeLine">
<el-input class="codeInput" @input="checkCodeFun" v-model="checkCode.code" placeholder="输入验证码" clearable
maxlength="4" />
<slideVerify class="codeShow" v-model:identifyCode="identifyCode" @click="refresh()"></slideVerify>
</div>
</el-form-item>
<!-- 登陆界面 -->
<el-form-item>
<div class="formInput settings">
<label class="labelText">
<input type="checkbox" v-model="rem_pswd" /> 记住密码
</label>
<label class="labelText">
<span @click.stop="go_page('findPassword')">忘记密码?</span>
<span @click.stop="go_page('register')">注册</span>
</label>
</div>
</el-form-item>
<!-- 提交按钮 -->
<el-form-item label-width="0">
<el-button :loading="state.loading" :disabled="!checkCode.isTrue" type="primary" class="loginButton" round
@click="handleSubmita(formRef)">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
后续
<script setup lang="ts">
import { UseLoginApi } from "@/api/login/index"
import { LoginState } from "@/api/login/types";
const useLoginApi = UseLoginApi()
const login = () => {
let data: LoginState = {
username: "admin",
password: "admin"
}
useLoginApi.login(data)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
}
import { reactive, ref } from 'vue';
const state = reactive({
loading: false,
formData: {
username: "admin",
password: "admin"
}
})
const formRef = ref<FormInstance>(); // 创建一个 ref 来引用表单
const formRules = reactive<FormRules<typeof state.formData>>({
username: [{ required: true, message: 'Please input activity form', trigger: 'blur' }],
password: [{ required: true, message: 'Please input activity form', trigger: 'blur' }],
})
// 记住密码,true 或 false
const rem_pswd = ref(localStorage.getItem('rem_pswd'))
const go_page = (path: string) => {
router.push({
name: 'login', // 替换为目标路由的名称或路径
query: {
path: path
}
})
}
const handleSubmita = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid: any, fields: any) => {
if (valid) {
login()
console.log('login');
} else {
console.log('error submit!', fields)
}
})
}
// 生成验证码以及检测
import slideVerify from "@/components/slideVerify.vue"
import { FormInstance, FormRules } from 'element-plus';
import router from '@/router';
interface CheckCodeType {
code: string;
isTrue: boolean;
}
// 生成随机验证码
const generateCode = () => {
const code = ref('');
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
for (let i = 0; i < 4; i++) {
code.value += characters.charAt(Math.floor(Math.random() * characters.length));
}
return code.value;
}
// 传递 验证码给子组件
const identifyCode = ref(generateCode())
// 刷新验证码
const refresh = () => {
identifyCode.value = generateCode()
}
// 检测输入的验证码,控制提交按钮是否可用
const checkCodeFun = (newValue: string) => {
if ((newValue as string).toUpperCase() === identifyCode.value) {
checkCode.isTrue = true
} else {
checkCode.isTrue = false
}
}
const checkCode = reactive<CheckCodeType>({
code: identifyCode.value,
isTrue: false
})
</script>
<style scoped>
/* 可以添加一些样式 */
.loginForm {
height: 400px;
border: 1px solid;
border-radius: 10px;
text-align: center;
background-color: #f3f3f3;
}
.loginForm .rloginTitle {
font-size: 23px;
margin: 5%;
}
.loginButton {
width: 250px;
margin: auto;
margin-top: 20px;
}
.formInput {
width: 80%;
min-width: 50px;
}
.codeLine {
display: flex;
}
.codeLine .codeInput {
flex: 2;
}
.codeLine .codeShow {
flex: 1;
}
.settings {
display: flex;
justify-content: space-between;
}
.settings label {
flex: 1;
font-size: 16px;
}
.settings .labelText {
cursor: pointer;
}
.btn-base {
width: 100%;
}
.btn-flex {
display: flex;
justify-content: center;
}
</style>
目前来说,直接测试是有效果的
2.测试效果
点击登陆后,收到的数据打印成功,
控制台-》网路查看,的确是访问了接口
三.一些说明修改
还记得之前安装pinia时,测试定义了一个userInfoState类型在stores/interface/index.ts里,
这个是用户信息的类型,我么这次转移到了api/login/types.ts里了,所以要有一些改动,
如果是跟接口访问的数据类型,或是接口返回的数据类型有关定义,我们可以在相应的
api/XXX/types.ts里定义,如果是其他的一些状态,我们再定义到stores/interface/index.ts里
1.stores/interface/index.ts修改
// 示例
export interface exampleState {
parmas1:string;
parmas2:string
}
2.stores/states.ts修改
// 在 src/store/index.js 中创建一个简单的 store
import { UserInfoState } from '@/api/login/types';
import { defineStore } from 'pinia'
interface State {
userForm: UserInfoState;
}
export const useMyStore = defineStore('myStore', {
state: (): State => ({
userForm: {
user: '',
ID: '',
age: '',
sex: ''
},
// 其他状态
}),
actions: {
// 动作
setUserInfo(data:UserInfoState) {
this.userForm = data
},
},
})