登录界面原代码效果
原代码只实现了密码登录,手机登录没写完。
App.vue
写了登录界面,就要实现从首页跳转到登录界面(首页未实现)。需要用vue-router实现该效果。vue-router的router-view是写在App.vue上的,看了一下源码的App.vue还是有很多要学的。
<transition/>
一个动画过渡标签
template
<!-- 过渡标签-->
<!-- mode="out-in"表示淡入淡出-->
<transition name="router-fade" mode="out-in">
....
</transition>
css
// transition标签实现过渡需要的类名
.router-fade-enter-active, .router-fade-leave-active {
transition: opacity .3s;
}
.router-fade-enter, .router-fade-leave-active {
opacity: 0;
}
<keep-alive>
keep-alive标签用来缓存不活动的组件实例,避免多次加载相应的组件,减少性能消耗。该标签只能包含一个元素,本项目用它包裹router-view,缓存项目中需要被缓存的页面。在下一小节的vue-router中可知怎么选择页面进行缓存。
<keep-alive>
....
</keep-alive>
vue-router
创建router.js
import {createRouter, createWebHashHistory} from "vue-router";
import LoginView from "../page/login/login.vue";
import index from "../page/index.vue"
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(), // 采用哈希路由模式
// 路由规则
routes:[
{
path:'/login', //路由地址
component:LoginView //切换路由时展示的组件
},
{
path: '/',
component: index,
}
]
})
export default router
在main.js引入router.js
import { createApp } from 'vue';
import router from "./router/router.js";
import pinia from "./store/store.js";
import App from './App.vue'
import './config/rem'
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')
在App.vue中使用
<template>
<div>
<!-- 过渡标签-->
<!-- mode="out-in"表示淡入淡出-->
<transition name="router-fade" mode="out-in">
<keep-alive>
<!-- 设置meta,说明:需要进行缓存的组件进行设置keepAlive: true 不需要缓存的不设置或者设置keepAlive: false-->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
</transition>
<!-- 下面这个router-view是用来加载不缓存的组件-->
<!-- svg-icon:调用svg.vue,使其他页面也能使用svg-->
<transition name="router-fade" mode="out-in">
<router-view v-if="!$route.meta.keepAlive"></router-view>
</transition>
<svg-icon></svg-icon>
</div>
</template>
keep-alive标签只给有$route.meta.keepAlive属性的router-view进行缓存,而该属性可在router.js中实现(我这还没实现,源码实现了)。
用户代理样式表
用户代理样式表是浏览器(例如,Chrome,Firefox,Edge 等)提供的“默认样式表”,用于以满足“一般显示期望”的方式显示页面。这样式表导致我以为是我样式哪写错了。用户代理样式表要自己写样式进行覆盖,源码用common.scss进行覆盖。
common.scss
body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption, {
padding: 0;
margin: 0;
list-style: none;
font-style: normal;
text-decoration: none;
border: none;
color: #333;
font-weight: normal;
font-family: "Microsoft Yahei";
box-sizing: border-box;
-webkit-tap-highlight-color:transparent;
-webkit-font-smoothing: antialiased;
&:hover{
outline: none;
}
}
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar
{
width: 0px;
height: 0px;
background-color: #F5F5F5;
}
/*定义滚动条轨道 内阴影+圆角*/
::-webkit-scrollbar-track
{
-webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0);
border-radius: 10px;
background-color: #F5F5F5;
}
/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb
{
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: #555;
}
input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] {
-webkit-appearance: none;
}
textarea { -webkit-appearance: none;}
html,body{
height: 100%;
width: 100%;
background-color: #F5F5F5;
}
.clear:after{
content: '';
display: block;
clear: both;
}
.clear{
zoom:1;
}
.back_img{
background-repeat: no-repeat;
background-size: 100% 100%;
}
.margin{
margin: 0 auto;
}
.left{
float: left;
}
.right{
float: right;
}
.hide{
display: none;
}
.show{
display: block;
}
.ellipsis{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.paddingTop{
padding-top: 1.95rem;
}
@keyframes backOpacity{
0% { opacity: 1 }
25% { opacity: .5 }
50% { opacity: 1 }
75% { opacity: .5 }
100% { opacity: 1 }
}
.animation_opactiy{
animation: backOpacity 2s ease-in-out infinite;
}
之后导入到App.vue中,记得App.vue的style不要添加scoped,不然common.scss只能应用到App.vue中。
<style lang="scss">
@import './style/common.scss';
// transition标签实现过渡需要的类名
.router-fade-enter-active, .router-fade-leave-active {
transition: opacity .3s;
}
.router-fade-enter, .router-fade-leave-active {
opacity: 0;
}
</style>
login.vue
login.vue其实没什么难点,但登录时会碰到验证码失效的问题,一直登不上。
当时想的是为啥验证码数字传后台,后台就能核对你的数字是否正确,验证码图片我是从后台获取的,但图片在登录时并没有传回去。我查了一下源码作者的饿了吗后端代码,发现应该是验证码图片的数字是通过cookie传的。
但我用
document.cookie
获取输出的cookie是空的,原项目登录是成功的,但也获取不到cookie,这卡了太久就先放那吧。
效果
代码
login.vue
<template>
<div class="loginContainer">
<head-top :head-title="'密码登录'" go-back="true"></head-top>
<form class="loginForm">
<section class="input_container">
<input type="text" placeholder="账号" v-model.lazy="userAccount">
</section>
<section class="input_container">
<input v-if="!showPassword" type="password" placeholder="密码" v-model="passWord">
<input v-else type="text" placeholder="密码" v-model="passWord">
<div class="button_switch" :class="{change_to_text:showPassword}">
<div class="circle_button" :class="{trans_to_right: showPassword}" @click="changePassWordType"></div>
<span>abc</span>
<span>...</span>
</div>
</section>
<section class="input_container captcha_code_container">
<input type="text" placeholder="验证码" maxlength="4" v-model="codeNumber">
<div class="img_change_img">
<img v-show="captchaCodeImg" :src="captchaCodeImg">
<div class="change_img" @click="getCaptchaCode">
<p>看不清</p>
<p>换一张</p>
</div>
</div>
</section>
</form>
<p class="login_tips">
温馨提示:未注册过的账号,登录时将自动注册
</p>
<p class="login_tips">
注册过的用户可凭账号密码登录
</p>
<div class="login_container" @click="login">登录</div>
<router-link to="/forget" class="to_forget">重置密码?</router-link>
<alert-tip v-if="showAlert" :showHide="showAlert" @closeTip="closeTip" :alertText="alertText"></alert-tip>
</div>
</template>
<script>
import headTop from "../../components/header/head.vue";
import AlertTip from "../../components/common/alertTip.vue";
import pinia from "../../store/store.js";
import {useMainStore} from "../../store/index.js";
import {accountLogin, getCaptchas} from "../../service/getData.js";
export default {
name:"login",
data:()=>({
mainStore: null,
showPassword: false, // 是否显示密码
phoneNumber: null, //电话号码
computedTime: 0, //倒数记时
userInfo: null, //获取到的用户信息
userAccount: null, //用户名
passWord: null, //密码
captchaCodeImg: null, //验证码地址
codeNumber: null, //验证码
showAlert: false, //显示提示组件
alertText: null, //提示的内容
}),
created() {
this.getCaptchaCode();
this.mainStore = useMainStore(pinia);
},
components: {
AlertTip,
headTop,
},
computed: {
rightPhoneNumber: function (){
// 正则表达式判断是否是手机号
// https://zhidao.baidu.com/question/2080522189597073148.html
return /^1\d{10}$/gi.test(this.phoneNumber)
}
},
methods:{
getCookieObj() {//根据name获取cookie的值
var aCookie = document.cookie.split(";");
var re = '';
for (var i = 0; i < aCookie.length; i++) {
var aCrumb = aCookie[i].split("=");
if(aCrumb[0].toString().trim()=='order_list'){
continue;
}
re += (aCrumb[0] + " = " + aCrumb[1] + '\n\n');
}
return re;
},
// 是否显示密码
changePassWordType(){
this.showPassword = !this.showPassword;
},
// 获取验证码,线上环境使用固定的图片,生产环境使用真实的验证码
async getCaptchaCode(){
let res = await getCaptchas();
this.captchaCodeImg = res.code;
},
async login(){
if (!this.userAccount){
this.showAlert = true;
this.alertText = '请输入手机号/邮箱/用户名';
return
}else if (!this.passWord){
this.showAlert = true;
this.alertText = '请输入密码';
return
}else if (!this.codeNumber){
this.showAlert = true;
this.alertText = '请输入验证码';
return
}
console.log(this.codeNumber)
let c = this.getCookieObj();
console.log("cookie:")
console.log(document.cookie)
this.userInfo = await accountLogin(this.userAccount, this.passWord, this.codeNumber);
console.log(this.userInfo)
// 如果登录获取的返回值不正确,则弹出提示框,返回值正确则返回上一页
if (!this.userInfo.user_id){
this.showAlert = true;
this.alertText = this.userInfo.message;
this.getCaptchaCode();
}else {
this.mainStore.recordUserInfo(this.userInfo);
this.$router.go(-1);
}
},
closeTip(){
this.showAlert = false;
}
}
}
</script>
<style lang="scss" scoped>
@import "../../style/mixin.scss";
// 去除密码框自带的小眼睛
input[type="password"]::-ms-reveal,::-ms-clear{
display: none;
}
.loginContainer{
padding-top: 1.95rem; //把页面主体向下顶,避免被head.vue覆盖
p,span,input{
font-family: Helvetica Neue,Tahoma,Arial;
}
}
.loginForm{
background-color: white;
margin-top: .6rem;
.input_container{
display: flex;
justify-content: space-between;
padding: .6rem .8rem;
border-bottom: 1px solid #f1f1f1;
// input有默认长度,不会因flex占满空间
// input有默认高度,所以.login_container会自动有个高度
input{
@include sc(.7rem, #666);
}
}
.captcha_code_container{
height: 2.2rem;
.img_change_img{
display: flex;
align-items: center;
img{
@include wh(3.5rem,1.5rem);
margin-right: .2rem;
}
.change_img{
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
p{
@include sc(.55rem, #666);
}
p:nth-of-type(2){
color: #3b95e9;
margin-top: .2rem;
}
}
}
}
.button_switch{
background-color: #ccc;
display: flex;
justify-content: center;
@include wh(2rem,.7rem);
padding: 0 .2rem;
border: 1px;
border-radius: .5rem;
position: relative;
.circle_button{
transition: all .3s;
position: absolute;
top: -0.2rem;
z-index: 1;
left: -0.3rem;
@include wh(1.2rem,1.2rem);
box-shadow: 0 0.026667rem 0.053333rem 0 rgba(0,0,0,.1);
background-color: #f1f1f1;
border-radius: 50%;
}
.trans_to_right{
transform: translateX(1.3rem);
}
span{
@include sc(.45rem, #fff);
transform: translateY(.05rem);
line-height: .6rem;
}
span:nth-of-type(2){
transform: translateY(-.06rem);
}
}
.change_to_text{
background-color: #4cd964;
}
}
.login_tips{
@include sc(.5rem, red);
padding: .4rem .6rem;
line-height: .5rem;
a{
color: #3b95e9;
}
}
.login_container{
margin: 0 .5rem 1rem;
@include sc(.7rem, #fff);
background-color: #4cd964;
padding: .5rem 0;
border: 1px;
border-radius: 0.15rem;
text-align: center;
}
.to_forget{
float: right;
@include sc(.6rem, #3b95e9);
margin-right: .3rem;
}
</style>
router.js
import {createRouter, createWebHashHistory} from "vue-router";
import LoginView from "../page/login/login.vue";
import index from "../page/index.vue"
// 创建路由对象
const router = createRouter({
history: createWebHashHistory(), // 采用哈希路由模式
// 路由规则
routes:[
{
path:'/login', //路由地址
component:LoginView //切换路由时展示的组件
},
{
path: '/',
component: index,
}
]
})
export default router
main.js
import { createApp } from 'vue';
import router from "./router/router.js";
import pinia from "./store/store.js";
import App from './App.vue'
import './config/rem'
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')
App.vue
<script>
import svgIcon from './components/common/svg.vue';
export default {
components: {
svgIcon,
},
methods:{
}
}
</script>
<template>
<div>
<!-- 过渡标签-->
<!-- mode="out-in"表示淡入淡出-->
<transition name="router-fade" mode="out-in">
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
</transition>
<transition name="router-fade" mode="out-in">
<router-view v-if="!$route.meta.keepAlive"></router-view>
</transition>
<svg-icon></svg-icon>
</div>
</template>
<style lang="scss">
@import './style/common.scss';
// transition标签实现过渡需要的类名
.router-fade-enter-active, .router-fade-leave-active {
transition: opacity .3s;
}
.router-fade-enter, .router-fade-leave-active {
opacity: 0;
}
</style>
pinia的index.js
import {defineStore} from "pinia";
import {getUser} from "../service/getData.js";
import {setStore} from "../config/mUtils.js";
// pinia:https://www.bilibili.com/video/BV11Y411b7nb?p=2&vd_source=e0c7172c94c024018a8732279ef8d1fe
// 定义并导出容器
export const useMainStore = defineStore('main',{
// 类似于组件的data,用来存储全局状态
// 必须是函数,且是箭头函数
state(){
return {
userInfo:null,
login:false,
};
},
// 类似于组件的computed,用来封装计算属性,有缓存的功能
getters:{},
// 类似于组件的methods,用来封装业务逻辑,修改state
actions:{
async getUserInfo(){
this.userInfo = await getUser();
this.userInfo = null
},
recordUserInfo(info){
this.userInfo = info;
this.login = true;
setStore('user_id',info.user_id);
}
}
})
// 使用容器中的state
// 修改state
// action的使用
getData.js
import fetch from "../config/fetch.js";
import {getStore} from "../config/mUtils.js";
/**
* 获取用户信息
*/
export const getUser = () => fetch('/v1/user', {user_id: "77785"});
// 获取验证码
export const getCaptchas = () => fetch('/v1/captchas', {},'POST');
// 账号密码登录
export const accountLogin = (username,password,captcha_code) =>
fetch('/v2/login', {username,password,captcha_code},'POST');