上一篇文章已经完成了对前端项目的相关配置,接下来便开始我们项目的开发。首先要做的是完成我们的登录页面以及相关接口的调用。
首页打开我们的接口文档地址:http://localhost:3000/fs_admin/api
我们只需要用到获取验证码
和用户登录
两个接口即可。然后在api/login/index.ts
写上这两个接口,因为这两个接口都不需要 token,所有我们只需要在request.ts
中设置isToken:false
。
import request from "@/utils/http/index";
import { LoginVo } from "./types/login.vo";
export const login = (data: LoginVo) => {
return request({
url: "/user/login",
data,
isToken: false,
method: "post",
});
};
//获取验证码
export const getCaptcha = () => {
return request({
url: "/user/captcha",
isToken: false,
method: "get",
});
};
其中LoginVo
是我们规定的传参格式
export type LoginVo = {
username: string,
password: string,
id: string,
captcha: string,
};
然后在views
目录下创建login/index.vue
文件,并配置好相关路由。
最终的界面如下图,其中扫码及其它登录后续会完善,这里先介绍账户密码登录
前端部分登录逻辑很简单,首先调用获取验证码接口拿到图片和 id(这里的图片是 svg 格式,可以直接当作元素插入,vue中使用v-html
指令)
import { login, getCaptcha } from "@/api/login";
...
//获取验证码
const codeUrl = ref<string>();
const handleGetCaptcha = async () => {
const { data } = await getCaptcha();
codeUrl.value = data.img;
formLogin.id = data.id;
};
handleGetCaptcha();
登录的时候将id
及验证码
传入即可获得token
,然后跳转至首页即可
const formLogin = reactive<LoginVo>({
username: "",
password: "",
id: "",
captcha: "",
});
const loginRules = reactive({
username: [{ required: true, message: "用户名不可为空", trigger: "blur" }],
password: [{ required: true, message: "密码不可为空", trigger: "blur" }],
});
...
//登录
const handleLogin = async () => {
const { data } = await login(formLogin);
Storage.set<string>("token", data);
if (isRemember.value) {
rememberPassword(formLogin);
} else {
Storage.remove("userAccount");
}
router.push("/");
};
后面所有需要 token 才能访问的接口都会在 header 中加上登录获取的 token,这里的逻辑在axio
封装的工具中utils/http/index.ts
除此之外,我们还需要记住密码的功能,每次登录如果勾选了记住密码则将其缓存在本地,下次进入页面直接读取。
/**
* 记住密码
* @param account 账户密码
*/
const rememberPassword = (account: LoginVo) => {
Storage.set < LoginVo > ("userAccount", account);
};
const isRemember = ref(false);
//获取记住的账户密码
const getRememberAccount = () => {
const userAccount: LoginVo | null = Storage.get("userAccount");
if (!userAccount) return;
formLogin.username = userAccount.username;
formLogin.password = userAccount.password;
isRemember.value = true;
};
最后判断一下登录过后的用户再次进入登录页面直接跳转至首页即可
onMounted(() => {
getRememberAccount();
if (Storage.get("token")) return router.push("/");
handleGetCaptcha();
});
ok,到这里我们就完成了前端部分的登录功能啦,完整代码如下
<template>
<div
class="flex items-center justify-center h-screen bg-gradient-to-b from-blue-500 to-blue-200"
>
<div class="flex mx-auto bg-white rounded py-10">
<div class="w-[330px] box-border text-center pt-10">
<div class="text-[#444444] font-bold">打开微信App</div>
<div class="text-sm pt-2 pb-6">右上角扫一扫</div>
<div
class="border border-gray rounded w-[50%] mx-auto overflow-hidden inline-block"
>
<img class="w-full" src="../../assets/login/gzh_code.jpg" alt="" />
</div>
<div class="pt-4 font-bold text-sm">扫码登录</div>
</div>
<div
class="w-[400px] pl-10 pr-10 pb-3 border-box border-l border-l-2 border-gray"
>
<div class="font-bold mb-3">密码登录</div>
<el-form :model="formLogin" :rules="loginRules">
<el-form-item prop="username">
<el-input
size="large"
placeholder="请输入用户名"
v-model="formLogin.username"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
size="large"
v-model="formLogin.password"
placeholder="请输入密码"
/>
</el-form-item>
<el-form-item prop="code">
<el-input
v-model="formLogin.captcha"
size="large"
auto-complete="off"
placeholder="验证码"
class="w-[60%] mr-4"
>
</el-input>
<div @click="handleGetCaptcha" v-html="codeUrl"></div>
</el-form-item>
<el-checkbox v-model="isRemember" label="记住密码" size="large" />
<el-button
type="primary"
class="w-full mt-5 !h-[40px]"
@click="handleLogin"
>登录</el-button
>
</el-form>
<div class="flex items-center justify-between mt-4">
<div class="w-[100px] border-t-2 border-gray"></div>
<div class="text-[#999] text-[14px]">其它方式登录</div>
<div class="w-[100px] border-t-2 border-gray"></div>
</div>
<div class="flex justify-around mt-4">
<div class="bg-[#f5f5f5] rounded-full p-2">
<wxIcon />
</div>
<div class="bg-[#f5f5f5] rounded-full p-2">
<qqIcon />
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import wxIcon from "@/assets/svg/wx_icon.vue";
import qqIcon from "@/assets/svg/qq_icon.vue";
import { reactive, ref, onMounted } from "vue";
import { login, getCaptcha } from "@/api/login";
import { LoginVo } from "@/api/login/types/login.vo";
import { Storage } from "@/utils/storage";
import { useRouter } from "vue-router";
const router = useRouter();
const formLogin = reactive<LoginVo>({
username: "",
password: "",
id: "",
captcha: "",
});
const loginRules = reactive({
username: [{ required: true, message: "用户名不可为空", trigger: "blur" }],
password: [{ required: true, message: "密码不可为空", trigger: "blur" }],
});
//获取验证码
const codeUrl = ref<string>();
const handleGetCaptcha = async () => {
const { data } = await getCaptcha();
codeUrl.value = data.img;
formLogin.id = data.id;
};
//登录
const handleLogin = async () => {
const { data } = await login(formLogin);
Storage.set<string>("token", data);
if (isRemember.value) {
rememberPassword(formLogin);
} else {
Storage.remove("userAccount");
}
router.push("/");
};
/**
* 记住密码
* @param account 账户密码
*/
const rememberPassword = (account: LoginVo) => {
Storage.set<LoginVo>("userAccount", account);
};
const isRemember = ref(false);
//获取记住的账户密码
const getRememberAccount = () => {
const userAccount: LoginVo | null = Storage.get("userAccount");
if (!userAccount) return;
formLogin.username = userAccount.username;
formLogin.password = userAccount.password;
isRemember.value = true;
};
onMounted(() => {
getRememberAccount();
if (Storage.get("token")) return router.push("/");
handleGetCaptcha();
});
</script>
完整代码地址登录模块(https://github.com/qddidi/fs-admin/commit/803ba726cac5b3748560103a9834d6daf77a7ab5)