放假不停歇,趁着假期学习下VUE3相关的内容,一方面是自己保持活力,另一方面也是工作需要,本系列是我的自学教程,如果有从0开始学习VUE3的,可以跟着一起练习下,毕竟前端我也是泥腿子出身,这一系列会使用Vite、TS、Pinia、Element-Plus等新知识点,既是查漏补缺,也是知识分享。
代码地址:
https://github.com/anjoy8/bcvp.vue3.git
这是每篇文章一节课一个分支,方便大家学习,会慢慢的将blog.admin项目进行翻新,使用的后端接口还是BlogCore。
系列文章:
本文参考的是开源项目
https://gitee.com/HalseySpicy/Geeker-Admin/tree/template
分步骤讲解登录逻辑,主要看着页面样式简约美观,作为element-plus的案例来说明,先看效果图:
在直接安装element-plus的依赖。
安装element-plus和sass-embedded
这个思路很像Netcore,安装完依赖,就在main.ts中注册服务:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// element css
import "element-plus/dist/index.css";
// element dark css
import "element-plus/theme-chalk/dark/css-vars.css";
// element plus
import ElementPlus from "element-plus";
// element icons
import * as Icons from "@element-plus/icons-vue";
import App from './App.vue'
import router from './router'
const app = createApp(App)
// register the element Icons component
Object.keys(Icons).forEach(key => {
app.component(key, Icons[key as keyof typeof Icons]);
});
app.use(ElementPlus)
app.use(createPinia())
app.use(router)
app.mount('#app')
这种写法对于习惯vue2的开发来说,还是不太舒服,直接用setup的语法糖更好理解一些:
<template>
<div class="login">
<h1>登录</h1>
<form @submit.prevent="onSubmit">
<div>
<label for="name">用户名</label>
<input v-model="loginForm.name" id="name" type="text" required />
</div>
<div>
<label for="pass">密码</label>
<input v-model="loginForm.pass" id="pass" type="password" required />
</div>
<button type="submit" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
</form>
<p v-if="errorMessage" class="error">{{ errorMessage }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { login } from '@/api/loginApi';
import type { LoginRequest, BaseResponse, LoginResponse } from '@/api/loginApi';
const router = useRouter();
const loginForm = ref<LoginRequest>({
name: '',
pass: '',
});
const loading = ref(false);
const errorMessage = ref<string | null>(null);
/**
* 登录表单提交处理函数
*/
const onSubmit = async () => {
loading.value = true;
errorMessage.value = null;
try {
const response: BaseResponse<LoginResponse> = await login(loginForm.value);
if (response.success) {
// 登录成功,跳转到首页或者其他页面
router.push({ name: 'Home' });
} else {
// 登录失败,显示错误信息
errorMessage.value = response.msg;
}
} catch (error) {
// 请求错误处理
errorMessage.value = '登录失败,请重试';
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.login {
max-width: 400px;
margin: 0 auto;
padding: 1rem;
}
.error {
color: red;
margin-top: 1rem;
}
</style>
修改App.vue入口文件内容:
<template>
<el-config-provider :locale="locale" :button="buttonConfig">
<router-view></router-view>
</el-config-provider>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import { ElConfigProvider } from "element-plus";
// element button config
const buttonConfig = reactive({ autoInsertSpace: false });
</script>
重新构建登录页面
在views文件夹下,新增一个login文件夹,然后新增components/LoginForm.vue、index.scss、index.vue共三个文件
其中index.vue核心内容:
<template>
<div class="login-container flx-center">
<div class="login-box">
<div class="login-left">
<img class="login-left-img" src="@/assets/images/login_left.png" alt="login" />
</div>
<div class="login-form">
<div class="login-logo">
<img class="login-icon" src="@/assets/images/logo.svg" alt="" />
<h2 class="logo-text">BCVP.VUE3</h2>
</div>
<LoginForm />
</div>
</div>
</div>
</template>
<script setup lang="ts" name="login">
import LoginForm from "./components/LoginForm.vue";
</script>
<style scoped lang="scss">
@import "./index.scss";
</style>
作为一个核心的登录布局,核心的逻辑都在Form表单LoginForm.vue中:
<template>
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" size="large">
<el-form-item prop="name">
<el-input v-model="loginForm.name" placeholder="账号:blogadmin">
<template #prefix>
<el-icon class="el-input__icon">
<user />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="pass">
<el-input v-model="loginForm.pass" type="password" placeholder="密码:blogadmin" show-password
autocomplete="new-password">
<template #prefix>
<el-icon class="el-input__icon">
<lock />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-form>
<div class="login-btn">
<el-button :icon="CircleClose" round size="large" @click="resetForm(loginFormRef)"> 重置 </el-button>
<el-button :icon="UserFilled" round size="large" type="primary" :loading="loading"
@click="loginModule(loginFormRef)">
登录
</el-button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { useRouter } from "vue-router";
import { ElNotification, ElMessage } from "element-plus";
import { CircleClose, UserFilled } from "@element-plus/icons-vue";
import type { ElForm } from "element-plus";
import { login } from '@/api/loginApi';
import type { LoginRequest, BaseResponse, LoginResponse } from '@/api/loginApi';
import { useAuthStore } from '@/stores/auth';
const router = useRouter();
const authStore = useAuthStore();
const loginForm = ref<LoginRequest>({
name: '',
pass: '',
});
type FormInstance = InstanceType<typeof ElForm>;
const loginFormRef = ref<FormInstance>();
const loginRules = reactive({
name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
pass: [{ required: true, message: "请输入密码", trigger: "blur" }]
});
const loading = ref(false);
// login
const loginModule = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async valid => {
if (!valid) return;
loading.value = true;
try {
// 1.执行登录接口
const response: BaseResponse<LoginResponse> = await login(loginForm.value);
if (response.success) {
// 保存 token 到 Pinia
authStore.setToken(response.response.token);
ElNotification({
title: '首页',
message: "欢迎登录 BCVP.VUE3",
type: "success",
duration: 3000
});
router.push({ name: 'about' });
} else {
// 登录失败,显示错误信息
ElMessage.error(response.msg || "请求失败!请您稍后重试");;
}
} finally {
loading.value = false;
}
});
};
// resetForm
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
onMounted(() => {
// 监听 enter 事件(调用登录)
document.onkeydown = (e: KeyboardEvent) => {
e = (window.event as KeyboardEvent) || e;
if (e.code === "Enter" || e.code === "enter" || e.code === "NumpadEnter") {
if (loading.value) return;
loginModule(loginFormRef.value);
}
};
});
</script>
<style scoped lang="scss">
@import "../index.scss";
</style>
重主题逻辑和之前vue2的差别不大,主要就是用到了element-plus的一些样式和结构,整体更美观
最后修改router路由地址,就可以看到不一样的页面效果了
下篇文章我们写一下另一个重中之重的重头戏,动态渲染左侧菜单,敬请期待。