Vue学习二——创建登录页面

news2025/1/14 15:51:26

前言

以一个登录页面为例子,这篇文章简单介绍了vue,element-plus的一些组件使用,vue-router页面跳转,pinia及持久化存储,axios发送请求的使用。后面的页面都大差不差,也都这么实现,只是内容,页面布局会变一些。其他插件功能,用到再介绍。

一、实现效果图

在这里插入图片描述

  1. 一个大的div,class=main
  2. 在main这个div中,有一个div,class=“login_box”
  3. login_box中,分上面部分,包含icon和标题;下面部分就是登录表单数据
<template>
	<div class="main">
		<div class="login_box">		
			<div class="head">
			</div>
			<div class="login-form" >
			</div>
		</div>
	</div>
</template>

二、页面内容

1.main

main只需要铺满整个页面,给个背景图就行。得到一个铺满页面的图片

.main{
		width: 100vw;
		height: 100vh;
		background: url('../../assets/bg.jpeg');
		background-size: cover;
	}

2.login_box

.login_box{
			width: 500px;
			height: 460px;
			box-shadow:  0 0 5px var(--el-color-primary);
			position:absolute;
			top:calc(50vh - 230px);
			left: calc(50vw - 250px);
			border-radius: 20px;
			background-color: rgba(255, 255, 255, 0.8);
	}

在这里插入图片描述

3. login_box -> head

<div class="head">
	<img src="@/assets/logo.jpeg" alt="logo" class="logo">
	<div class="title">接口自动化平台</div>
</div>
.head{
		display: flex;
		justify-content: center;
		align-items: center;
		height: 150px;
		.logo{
			width: 60px;
			height: 60px;
			border-radius: 50px;
		}
		.title{
			font-size: 30px;
			font-weight: bold;
			color: var(--el-color-primary);
			margin-left: 20px;
		}
	}

在这里插入图片描述
4. login_box -> login_form

<div class="login-form" >
	  <el-form :model="loginForm">
	    <el-form-item >
	      <el-input prefix-icon="User" v-model="loginForm.username" 
					placeholder="请输入用户名"
		  />
	    </el-form-item>
		
		<el-form-item >
		  <el-input type="password" prefix-icon="Lock" v-model="loginForm.password" 
					placeholder="请输入密码"
		  />
		</el-form-item>
		
		<el-form-item>
		      <el-switch v-model="loginForm.status" inactive-text='记住登录状态' />
		</el-form-item>
		
	    <el-form-item>
	      <el-button  type="primary"  @click="LoginSubmit(loginFormRef)" style="width: 100%;">登 录</el-button>
	    </el-form-item>
		
		<el-link @click="router.push('register')" type="primary">没有账号?点击注册</el-link>
	  </el-form>
</div>
.login-form{
			margin: 0 30px;
		}

在这里插入图片描述

三、数据绑定

1. 动态数据绑定

使用 v-bind:(可以简写为:) 来进行动态数据绑定,

例如:将这里的登录按钮,是否可以点击设计为只有用户名和密码都不空才能点击。

可以设置disable属性,给这个属性动态绑定一个值‘canClick’,这个值是计算出来的

<el-form-item>
	      <el-button :disabled='canClick' type="primary"  @click="LoginSubmit(loginFormRef)" style="width: 100%;">登 录</el-button>
</el-form-item>


	// 未输入账号密码禁用登录
const canClick = computed(()=>{
	if (loginForm.username!=='' && loginForm.password!==''){return false}
	else{return true}
	})

计算属性computed

此时,没有输入账号密码,登录按钮就不能点击了。
在这里插入图片描述

更多关于计算属性可以参考: vue官网-计算属性

2. 双向绑定

v-model=" " 这样进行数据双向绑定
 <el-input type="password" prefix-icon="Lock" v-model="loginForm.password" 
					placeholder="请输入密码"/>
...
<script setup>
const loginForm = reactive({
	username:"",
	password:"",
	status:true	
})
</script>

将input输入框的内容与loginForm.password双向绑定。

3. 响应式数据

ref 和 reactive 我自己理解来说,就是ref定义简单一个具有响应性类型数据,比如字符串,布尔值,数值。而reactive可以定义复合的内容。在更改他们值的时候,页面显示也会同时更改

他们区别简单来说,就是reactive只存储对象,而ref存储基本数据类型,比如数字、字符串等。
更多可以参考其他文档,这里不主要介绍这块,因为我自己也不是很懂,平时拿来简单用用就是了,没深入了解过。嘿嘿

ref和reactive官网介绍
ref和reactive区别

四、Element表单数据校验

elment-plus表单校验

1. 创建表单校验规则

Form 组件提供了表单验证的功能,只需为 rules 属性传入约定的验证规则,并将 form-Item 的 prop 属性设置为需要验证的特殊键值即可

	//表单校验规则
	const loginRules = reactive({
		username: [
			{ required: true, message: '账号不能为空', trigger: 'blur' },
			{ min: 4, max: 18, message: '账号长度4-18位', trigger: 'blur' },
		],
		password :[
			{ required: true, message: '密码不能为空', trigger: 'blur' },
			{ min: 6, max: 18, message: '密码长度6-18位', trigger: 'blur' },
		]
	})

2. 给Form组件加上规则

< el-form model="loginForm" :rules="loginRules" >

3. 将 form-item 的 prop 属性设置为需要验证的特殊键值

例如:

<el-form-item prop="username">
	<el-input prefix-icon="User" v-model="loginForm.username" 
		placeholder="请输入用户名"/>
</el-form-item>

<el-form-item prop="password">
	<el-input type="password" prefix-icon="Lock" 
	v-model="loginForm.password" placeholder="请输入密码"/>
</el-form-item>

校验规则是光标移出输入框校验,所以没有输入用户名,点到密码。就会提示自定义的校验规则内容
在这里插入图片描述

五、发送请求(后端接口使用之前fastapi项目)

在api/module/UserApi.js里,定义所有用户相关的接口

1. 用户登录模块接口

// api/module/UserApi.js
import request from "@/api/request";

//导出请求方法
export default {
	//登录
	loginApi(data){
		return request.post('/api/users/login/',data)
	},
}

在这里插入图片描述

这个接口是之前fastapi栏写的,这里直接用。
在这里插入图片描述

2. 使用表单引用对象

在点击登录时,将表单引用对象传过去。首先进行表单预校验,预校验通过后,再调用接口,这样不会浪费资源

<el-form :model="loginForm" :rules="loginRules" ref='loginFormRef'>
......
<el-form-item>
	<el-button :disabled='canClick' type="primary"  @click="LoginSubmit(loginFormRef)" style="width: 100%;">登 录</el-button>
</el-form-item>

<script setup>
	import http from '@/api/index'
	...
	const loginFormRef=ref(null)
	...
	const LoginSubmit = async(loginRef)=>{
		//表单预校验
		await loginRef.validate(async (valid)=>{
			if(valid){
				const response = await http.user.loginApi(loginForm)
				if(response.status === 200){
					  ElNotification({
						title: '登录成功',
						message: '您已登录成功,进入首页',
						type: 'success',
						duration: 3,
					  })
					}else{
						ElMessage({
							message: response.data.detail,
							type: 'error',
							duration: 3,
						})
					}
			}
		})
	}
</script>

elemet-plus通知消息弹窗

为了页面效果更美观,点击后得到反馈结果。使用了这个消息弹窗

import { ElNotification,ElMessage} from 'element-plus'

Feedback 反馈组件

3. 发送请求(跨域问题)

在这里插入图片描述
能看到这里发送请求成功了,前端请求这块是没问题了。
应该是跨域配置有问题,一般是服务端配置问题。
重新配置了一下服务端,现在可以正常访问了。

# pyhton服务端
origins = ['http://127.0.0.1:5173',"http://localhost:5173"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
    allow_headers=["*"],
)

在这里插入图片描述

六、pinia数据持久化

1. 下载pinia-plugin-persist

npm install pinia-plugin-persist

2.注册插件

//main.js

import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'

// 创建一个pinia
const pinia = createPinia()

// 将持久化的插件注册到pinia中
pinia.use(piniaPluginPersist)

app.use(pinia)

3.在store中设置

在store下创建UserStore.js用来存储用户相关的信息,
使用localStorage来存储数据,也可以使用sessionStorage,看个人需求。

  • localStorage:数据的持久化程度高,即使用户关闭浏览器后,数据依然会被保留。除非显式删除,数据可以一直存在,适用于持久保存用户偏好设置、用户认证信息等需要长时间保存的数据。
  • sessionStorage:数据仅在浏览器的会话期间有效。一旦关闭页面或标签页,数据就会被清除,适用于存储临时性的数据,如表单状态、页面之间的传递数据等。

在这里插入图片描述

import {defineStore} from 'pinia'

export const UserStore = defineStore('ustore',{
	//全局数据
	state:()=>{
		return {
			token:null,
			userinfo:null,
			isLogin:false
		}
	},
	// 定义持久化配置
	persist:{
		enabled:true,
		strategies:[
			{
				key:'userInfo',
				storage:localStorage
			}
		]
	}
	
})

4. 在需要用到的地方进行使用

以登录成功为例,登录后保存用户token和用户信息。

//LoginView.vue

import{UserStore} from '@/stores/UserStore'

//创建用户store对象
const uStore = UserStore()
...

LoginSubmit()=>{...
...
//登录成功后保存用户信息
	//保存用户token和用户信息
	 uStore.token = response.data.token
	 uStore.userinfo = response.data.user
	 // 修改认证状态
	 if(loginForm.status){
	  uStore.isLogin = true
	 }
 }

最开始localStorage没有存储数据
在这里插入图片描述
登录成功后,就把数据记录下来了key:'userInfo'
在这里插入图片描述

七、页面跳转及其他页面使用pinia存储的数据

实现一个功能:登录成功后,需要跳转首页,首页展示token信息。

1. 创建一个home页面

在这里插入图片描述

2. 注册路径

在router/index中注册home的路径
在这里插入图片描述

3. 登录成功跳转

在登录页面,进行跳转

import {useRouter} from 'vue-router'
	
//创建路由对象
const router = useRouter()

...
//登录成功,跳转home页面

router.push({name:"home"})

在这里插入图片描述

项目相关部分代码

LoginView.vue

<template>
	<div class="main">
		<div class="login_box">		
			<div class="head">
				<img src="@/assets/logo.jpeg" alt="logo" class="logo">
				<div class="title">接口自动化平台</div>
			</div>
			<div class="login-form" >
				  <el-form :model="loginForm" :rules="loginRules" ref='loginFormRef'>
				    <el-form-item prop="username">
				      <el-input prefix-icon="User" v-model="loginForm.username" 
								placeholder="请输入用户名"
					  />
				    </el-form-item>
					
					<el-form-item prop="password">
					  <el-input type="password" prefix-icon="Lock" v-model="loginForm.password" 
								placeholder="请输入密码"
					  />
					</el-form-item>
					
					<el-form-item>
					      <el-switch v-model="loginForm.status" inactive-text='记住登录状态' />
					</el-form-item>
					
				    <el-form-item>
				      <el-button :disabled='canClick' type="primary"  @click="LoginSubmit(loginFormRef)" style="width: 100%;">登 录</el-button>
				    </el-form-item>
					
					<el-link @click="router.push('register')" type="primary">没有账号?点击注册</el-link>
				  </el-form>
			</div>
		</div>
	</div>
</template>

<script setup>
	import {reactive,ref,computed} from 'vue'
	import http from '@/api/index'
	import { ElNotification,ElMessage} from 'element-plus'
	import{UserStore} from '@/stores/UserStore'
	import {useRouter} from 'vue-router'
	
	//创建路由对象
	const router = useRouter()
	
	//创建用户store对象
	const uStore = UserStore()
	
	const loginFormRef=ref(null)
	
	const loginForm = reactive({
		username:"",
		password:"",
		status:true	
	})
	const canClick = computed(()=>{
		if (loginForm.username!=='' && loginForm.password!==''){return false}
		else{return true}
		})
	
	//表单校验规则
	const loginRules = reactive({
		username: [
			{ required: true, message: '账号不能为空', trigger: 'blur' },
			{ min: 4, max: 18, message: '账号长度4-18位', trigger: 'blur' },
		],
		password :[
			{ required: true, message: '密码不能为空', trigger: 'blur' },
			{ min: 6, max: 18, message: '密码长度6-18位', trigger: 'blur' },
		]
	})
	
	const LoginSubmit = async(loginRef)=>{
		console.log('click!!!')
		//表单预校验
		await loginRef.validate(async (valid)=>{
			if(valid){
				const response = await http.user.loginApi(loginForm)
				if(response.status === 200){
					  ElNotification({
						title: '登录成功',
						message: '您已登录成功,进入首页',
						type: 'success',
						duration: 3000,
					  })
					  //保存用户token和用户信息
					  uStore.token = response.data.token
					  uStore.userinfo = response.data.user
					  // 修改认证状态
					  if(loginForm.status){
						  uStore.isLogin = true
					  }
					  // 跳转到home页面
					  router.push({name:"home"})
					}else{
						ElMessage({
							message: response.data.detail,
							type: 'error',
							duration: 3,
						})
					}
			}
		})
	}
</script>

<style scoped lang='scss'>
	.main{
		width: 100vw;
		height: 100vh;
		/* background: url('../assets/hz.svg'); */
		background: url('../../assets/bg.jpeg');
		background-size: cover;
		.login_box{
			width: 500px;
			height: 460px;
			box-shadow:  0 0 5px var(--el-color-primary);
			position:absolute;
			top:calc(50vh - 230px);
			left: calc(50vw - 250px);
			border-radius: 20px;
			background-color: rgba(255, 255, 255, 0.8);
		}
		.head{
			display: flex;
			justify-content: center;
			align-items: center;
			height: 150px;
			.logo{
				width: 60px;
				height: 60px;
				border-radius: 50px;
			}
			.title{
				font-size: 30px;
				font-weight: bold;
				color: var(--el-color-primary);
				margin-left: 20px;
			}
		}
		.login-form{
			margin: 0 30px;
		}
	}

</style>

stores/UserStore.js

import {defineStore} from 'pinia'

export const UserStore = defineStore('ustore',{
	//全局数据
	state:()=>{
		return {
			token:null,
			userinfo:null,
			isLogin:false
		}
	},
	// 定义持久化配置
	persist:{
		enabled:true,
		strategies:[
			{
				key:'userInfo',
				storage:localStorage
			}
		]
	}
	
})

router/index.js

import { createRouter, createWebHistory } from 'vue-router'
// import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
	{
	  path:'/',
	  redirect:'/login'
	},
    {
      path: '/login',
      name: 'login',
      component: () => import('../views/User/LoginView.vue'),
    },
	{
	  path: '/home',
	  name: 'home',
	  component: () => import('../views/Home/HomeView.vue'),
	},
  ], 
})

export default router

main.js

import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'

import App from './App.vue'
import router from './router'

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

//语言国际化
import zhCn from 'element-plus/es/locale/lang/zh-cn'

//icon组件
import * as ElementPlusIconsVue from '@element-plus/icons-vue'



const app = createApp(App)

//注册element-plus语言国际化
app.use(ElementPlus, {
  locale: zhCn,
})

//注册elemnt-plus的icon组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}


// 创建一个pinia
const upinia = createPinia()

// 将持久化的插件注册到pinia中
upinia.use(piniaPluginPersist)

app.use(upinia)
app.use(router)

app.mount('#app')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2276549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初始Django框架

初识Django Python知识点&#xff1a;函数、面向对象。前端开发&#xff1a;HTML、CSS、JavaScript、jQuery、BootStrap。MySQL数据库。Python的Web框架&#xff1a; Flask&#xff0c;自身短小精悍 第三方组件。Django&#xff0c;内部已集成了很多组件 第三方组件。【主要…

深度学习每周学习总结R4(LSTM-实现糖尿病探索与预测)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客R6中的内容&#xff0c;为了便于自己整理总结起名为R4&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 目录 0. 总结1. LSTM介绍LSTM的基本组成部分如何理解与应用LSTM 2. 数据预处理3. 数…

虚假星标:GitHub上的“刷星”乱象与应对之道

在开源软件的世界里&#xff0c;GitHub无疑是最重要的平台之一。它不仅是一个代码托管平台&#xff0c;也是一个社交网络&#xff0c;允许开发者通过“点赞”&#xff08;即加星&#xff09;来表达对某个项目的喜爱和支持&#xff0c;“星标”&#xff08;Star&#xff09;则成…

前端笔记----

在我的理解里边一切做页面的代码都是属于前端代码。 之前用过qt框架&#xff0c;也是用来写界面的&#xff0c;但是那是用来写客户端的&#xff0c;而html是用来写web浏览器的&#xff0c;相较之下htmlcssJavaScript写出来的界面是更加漂亮的。这里就记录我自个学习后的一些笔…

【面试题】技术场景 4、负责项目时遇到的棘手问题及解决方法

工作经验一年以上程序员必问问题 面试题概述 问题为在负责项目时遇到的棘手问题及解决方法&#xff0c;主要考察开发经验与技术水平&#xff0c;回答不佳会影响面试印象。提供四个回答方向&#xff0c;准备其中一个方向即可。 1、设计模式应用方向 以登录为例&#xff0c;未…

2025华数杯国际赛A题完整论文讲解(含每一问python代码+数据+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2025“华数杯”国际大学生数学建模竞赛A题Can He Swim Faster的完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文…

关闭window10或11自动更新和自带杀毒

关闭window10或11自动更新和自带杀毒 1.关闭系统更新**修改组策略关闭自动更新****修改服务管理器关闭自动更新** 2.关闭系统杀毒 为什么需要关闭更新和杀毒 案例&#xff1a; #装完驱动隔一段时间就掉 #一些设置隔一段时间就重置了 #防止更新系统后有时卡 1.关闭系统更新 我…

解析OVN架构及其在OpenStack中的集成

引言 随着云计算技术的发展&#xff0c;虚拟化网络成为云平台不可或缺的一部分。为了更好地管理和控制虚拟网络&#xff0c;Open Virtual Network (OVN) 应运而生。作为Open vSwitch (OVS) 的扩展&#xff0c;OVN 提供了对虚拟网络抽象的支持&#xff0c;使得大规模部署和管理…

【ArcGIS技巧】如何给CAD里的面注记导入GIS属性表中

前面分享了GIS怎么给田块加密高程点&#xff0c;但是没有分享每块田的高程对应的是哪块田&#xff0c;今天结合土地整理软件GLAND做一期田块的属性怎么放入GIS属性表当中。 1、GLAND数据 杭州阵列软件&#xff08;GLand&#xff09;是比较专业的土地整理软件&#xff0c;下载之…

Excel中SUM求和为0?难道是Excel有Bug!

大家好&#xff0c;我是小鱼。 在日常工作中有时会遇到这样的情况&#xff0c;对Excel表格数据进行求和时&#xff0c;结果竟然是0&#xff0c;很多小伙伴甚至都怀疑是不是Excel有Bug&#xff01;其实&#xff0c;在WPS的Excel表格中数据求和&#xff0c;结果为0无法正确求和的…

Spring MVC简单数据绑定

【图书介绍】《SpringSpring MVCMyBatis从零开始学&#xff08;视频教学版&#xff09;&#xff08;第3版&#xff09;》_springspringmvcmybatis从零开始 代码、课件、教学视频与相关软件包下载-CSDN博客 《SpringSpring MVCMyBatis从零开始学(视频教学版)&#xff08;第3版&…

蓝桥杯备考:数据结构之栈 和 stack

目录 栈的概念以及栈的实现 STL 的stack 栈和stack的算法题 栈的模板题 栈的算法题之有效的括号 验证栈序列 后缀表达式 括号匹配 栈的概念以及栈的实现 栈是一种只允许在一端进行插入和删除的线性表 空栈&#xff1a;没有任何元素 入栈&#xff1a;插入元素消息 出…

使用Dify创建个问卷调查的工作流

为啥要使用Dify创建工作流呢&#xff1f;一个基于流程的智能体的实现&#xff0c;特别是基于业务的实现&#xff0c;使用Dify去实现时&#xff0c;通常都是一个对话工作流&#xff0c;当设计到相对复杂一些的流程时&#xff0c;如果将所有逻辑都放在对话工作流中去实现&#xf…

QT Quick QML 实例之椭圆投影,旋转

文章目录 一、前言二、演示三、部分代码与分析 QML 其它文章请点击这里: QT QUICK QML 学习笔记 国际站点 GitHub: https://github.com/chenchuhan 国内站点 Gitee : https://gitee.com/chuck_chee 一、前言 此 Demo 主要用于无人机吊舱视角的模拟&#xf…

高通,联发科(MTK)等手机平台调优汇总

一、常见手机型号介绍&#xff1a; ISP除了用在安防行业&#xff0c;还有手机市场&#xff0c;以及目前新型的A/VR眼睛&#xff0c;机器3D视觉机器人&#xff0c;医疗内窥镜这些行业。 下面是一些最近几年发布的,,,旗舰SOC型号&#xff1a; 1.联发科&#xff1a;天玑92…

AI的崛起:它将如何改变IT行业的职业景象?

随着人工智能&#xff08;AI&#xff09;的快速发展&#xff0c;许多人开始担忧其对IT行业的影响&#xff0c;担心AI的出现可能会导致大量IT从业者失业。然而&#xff0c;事实并非如此简单&#xff0c;AI的崛起将为IT行业带来深刻的变革&#xff0c;既有挑战&#xff0c;也有机…

【25考研】西南交通大学软件工程复试攻略!

一、复试内容 复试对考生的既往学业情况、外语听说交流能力、专业素质和科研创新能力&#xff0c;以及综合素质和一贯表现等进行全面考查,主要考核内容包括思想政治素质和道德品质、外语听说能力、专业素质和能力&#xff0c;综合素质及能力。考核由上机考试和面试两部分组成&a…

玩转大语言模型——langchain调用ollama视觉多模态语言模型

系列文章目录 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 langchain调用ollama视觉多模态语言模型 系列文章目录前言使用Ollama下载模型查找模型下载模型 测试模型ollama测试langchain测试加载图片加载模型…

Android Dex VMP 动态加载加密指令流

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 上一篇【详解如何自定义 Android Dex VMP 保护壳】实现了 VMP 保护壳。 为了进一步加强对 dex 指令的保护&#xff0c;实现指令流加密和动态加载&#xff0c;…

浅谈云计算08 | 基本云架构

浅谈基本云架构 一、负载分布架构二、资源池架构三、动态可扩展架构四、弹性资源容量架构五、服务负载均衡架构六、云爆发架构七、弹性磁盘供给架构八、冗余存储架构 在当今数字化时代&#xff0c;云计算已成为企业发展的核心驱动力&#xff0c;而其背后的一系列关键架构则是支…