今日开发任务
-
首页搜索跳转功能
- 搜索框布局
- 点击 tab 进行切换
- 点击搜索跳转页面
-
登录表单布局
登录页 tab 切换 登录/注册
根据当前激活项, 显示 loginform / registerform
利用 el-form 布局登录表单
-
实现登录功能
绑定数据
校验输入
发送请求
-
掌握使用store管理用户登录数据 (vuex)
- 初步简介 store state mutations
- 实现 vuex 数据的初始化, 渲染和修改
- 登录完毕将用户数据存放到 vuex , 头部组件判断数据进行显示
- 利用插件 vuex-persistedstate 保持 vuex 数据持久化
首页搜索框和搜索跳转
思路
-
添加搜索框布局, 定位方式居中
扫描二维码关注公众号,回复: 12835682 查看本文章 -
搜索框
tab
切换执行不同的操作凡是有 tab 切换,第一时间想到有一个记录当前状态的变量
-
搜索跳转
实现步骤
1.搜索框布局
这里面的代码复制之前,要分辨那些需要
2.tab 高亮切换3个重点
-
有一个 data 变量储存当前激活状态 currentOption
-
tab 增加点击事件, 改变 currentOption
-
动态给当前激活的 tab 添加 class
把搜索框定位在轮播图上,在pages/index.vue
的template
新增以下代码:
<template>
<div class="container">
<!-- 幻灯片 -->
<!-- 省略代码 -->
<!-- 搜索框 -->
<div class="banner-content">
<div class="search-bar">
<!-- tab栏 -->
<el-row
type="flex"
class="search-tab">
<span
v-for="(item, index) in options"
:key="index"
:class="{active: index === currentOption}"
@click="handleOption(index)">
<i>{
{
item.name}}</i>
</span>
</el-row>
<!-- 输入框 -->
<el-row
type="flex"
align="middle"
class="search-input">
<input
:placeholder="options[currentOption].placeholder"
v-model="searchValue"
@keyup.enter="handleSearch"/>
<i class="el-icon-search" @click="handleSearch"></i>
</el-row>
</div>
</div>
</div>
</template>
在pages/index.vue
的script
替换如下:
<script>
export default {
data(){
return {
banners: [], // 轮播图数据
options: [ // 搜索框tab选项
{
name: "攻略",
placeholder: "搜索城市",
pageUrl: "/post?city="
},
{
name: "酒店",
placeholder: "请输入城市搜索酒店",
pageUrl: "/hotel?city="},
{
name: "机票",
placeholder: "请输入出发地",
pageUrl: "/air"
}
],
searchValue: "", // 搜索框的值
currentOption: 0, // 当前选中的选项
}
},
mounted(){
this.$axios({
url: "/scenics/banners"
}).then(res => {
const {
data} = res.data;
this.banners = data;
})
},
methods: {
handleOption(index){
},
handleSearch(){
}
},
}
</script>
在pages/index.vue
的style
替换如下:
<style scoped lang="less">
.container{
min-width:1000px;
margin:0 auto;
position:relative;
/deep/ .el-carousel__container{
height:700px;
}
.banner-image{
width:100%;
height:100%;
}
.banner-content{
z-index:9;
width:1000px;
position:absolute;
left:50%;
top:45%;
margin-left: -500px;
border-top:1px transparent solid;
.search-bar{
width:552px;
margin:0 auto;
}
.search-tab{
.active{
i{
color:#333;
}
&::after{
background: #eee;
}
}
span{
width:82px;
height:36px;
display:block;
position: relative;
margin-right:8px;
cursor: pointer;
i{
position:absolute;
z-index:2;
display: block;
width:100%;
height:100%;
line-height:30px;
text-align:center;
color:#fff;
}
&:after{
position: absolute;
left:0;
top:0;
display:block;
content: "";
width:100%;
height:100%;
border: 1px rgba(255,255,255,.2) solid;
border-bottom: none;
transform: scale(1.1,1.3) perspective(.7em) rotateX(2.2deg);
transform-origin: bottom left;
background: rgba(0,0,0,.5);
border-radius:1px 2px 0 0;
box-sizing:border-box;
}
}
}
.search-input{
width:550px;
height:46px;
background:#fff;
border-radius: 0 4px 4px 4px;
border: 1px rgba(255,255,255,.2) solid;
border-top:none;
box-sizing: unset;
input{
flex:1;
height:20px;
padding: 13px 15px;
outline: none;
border:0;
font-size:16px;
}
.el-icon-search{
cursor :pointer;
font-size:22px;
padding:0 10px;
font-weight:bold;
}
}
}
}
</style>
完成 上面步骤可以得到搜索框的静态布局,下面我们来加入交互操作。
2.tab栏操作
实现切换效果,并且判断如果切换的机票tab
,那么直接跳转到机票首页
编辑methods
下的handleOption
方法
// 省略其他代码
// 切换tab栏时候触发
handleOption(index){
// 设置当前tab
this.currentOption = index;
// 如果切换的机票tab,那么直接跳转到机票首页
const item = this.options[index];
if(item.name === "机票"){
return this.$router.push(item.pageUrl);
}
},
// 省略其他代码
搜索跳转
在输入时按下回车键,或者点击搜索放大镜图标, 触发搜索时候会跳转到当前tab
的pageUrl
页面路径,并且在url
上携带上输入框的值
页面会根据参数进行搜索请求
// 省略其他代码
// 搜索时候触发
handleSearch(){
const item = this.options[this.currentOption];
// 跳转时候给对应的页面url加上搜索内容参数
this.$router.push(item.pageUrl + this.searchValue);
}
// 省略其他代码
总结
-
先把搜索框定位在轮播图上。 (css 的问题)
-
给
tab
添加切换效果,并且判断如果是机票tab
,直接跳转到机票首页。最重要的一个变量是 currentOption
-
实现搜索跳转,注意跳转的链接来自当前选中的
tab
的url
属性,并且附带上参数
登录注册页布局
首先添加 切换的 tab 分类部分
新建pages/user/login.vue
的代码如下
<template>
<div class="container">
<!-- 主要内容 -->
<el-row
type="flex"
justify="center"
align="middle"
class="main">
<div class="form-wrapper">
<!-- 表单头部tab -->
<el-row type="flex" justify="center" class="tabs">
<span :class="{active: currentTab === index}"
v-for="(item, index) in [`登录`, `注册`]"
:key="index"
@click="handleChangeTab(index)">
{
{
item}}
</span>
</el-row>
<!-- 登录功能组件 -->
<!-- <LoginForm v-if="currentTab == 0"/> -->
<!-- 注册功能组件 -->
<!-- <RegisterForm v-if="currentTab == 1"/> -->
</div>
</el-row>
</div>
</template>
<script>
export default {
data(){
return {
currentTab: 0
}
},
methods: {
handleChangeTab(index){
this.currentTab = index;
},
}
}
</script>
<style scoped lang="less">
.container{
background:url(http://157.122.54.189:9095/assets/images/th03.jfif) center 0;
height: 700px;
min-width:1000px;
.main{
width:1000px;
height: 100%;
margin:0 auto;
position: relative;
.form-wrapper{
width:400px;
margin:0 auto;
background:#fff;
box-shadow: 2px 2px 0 rgba(0,0,0,0.1);
overflow:hidden;
.tabs{
span{
display: block;
width:50%;
height: 50px;
box-sizing: border-box;
border-top:2px #eee solid;
background:#eee;
line-height: 48px;
text-align: center;
cursor: pointer;
color:#666;
&.active{
color:orange;
border-top-color: orange;
background:#fff;
font-weight: bold;
}
}
}
}
}
}
</style>
在预留的位置中将会导入登录组件和注册组件。
登录功能
思路
-
在
components/user
中新建loginForm.vue
表单组件 -
使用
Element-ui
的表单组件布局 -
表单数据绑定 v-model
-
表单验证
输入框失去焦点,需要验证
点击登录按钮, 发送登录请求前, 需要一次整个表格的验证
使用饿了么封装好的验证方法
-
登录接口对接 ajax
实现步骤
新建登录表单组件
在components/user
中新建loginForm.vue
组件,新增内容如下
<template>
<el-form
:model="form"
ref="form"
:rules="rules"
class="form">
<el-form-item class="form-item">
<el-input
placeholder="用户名/手机">
</el-input>
</el-form-item>
<el-form-item class="form-item">
<el-input
placeholder="密码"
type="password">
</el-input>
</el-form-item>
<p class="form-text">
<nuxt-link to="#">忘记密码</nuxt-link>
</p>
<el-button
class="submit"
type="primary"
@click="handleLoginSubmit">
登录
</el-button>
</el-form>
</template>
<script>
export default {
data(){
return {
// 表单数据
form: {},
// 表单规则
rules: {},
}
},
methods: {
// 提交登录
handleLoginSubmit(){
console.log(this.form)
}
}
}
</script>
<style scoped lang="less">
.form{
padding:25px;
}
.form-item{
margin-bottom:20px;
}
.form-text{
font-size:12px;
color:#409EFF;
text-align: right;
line-height: 1;
}
.submit{
width:100%;
margin-top:10px;
}
</style>
注意:新增了组件后在
pages/user/login.vue
中导入即可,导入位置,去除下面部分的组件的注释
<!-- 登录功能组件 -->
<!-- <LoginForm v-if="currentTab == 0"/> -->
表单数据绑定
修改data
的form
数据,然后使用v-model
绑定到对应的表单字段。
编辑components/user/loginForm.vue
// 其他代码...
data(){
return {
// 表单数据
form: {
username: "", // 登录用户名/手机
password: "" // 登录密码
},
// 其他代码...
}
},
// 其他代码...
使用v-model
绑定到对应的表单字段
<!-- 其他代码... -->
<el-form-item class="form-item">
<!-- 新增了v-model -->
<el-input
placeholder="用户名/手机"
v-model="form.username">
</el-input>
</el-form-item>
<el-form-item class="form-item">
<!-- 新增了v-model -->
<el-input
placeholder="密码"
type="password"
v-model="form.password">
</el-input>
</el-form-item>
<!-- 其他代码... -->
表单验证
双向数据绑定到form
字段后,我们现在可以来提交表单进行登录了,但是提交之前还需要验证表单字段是否合法,比如不能为空。
表单验证3个重点的设定, 这是用来做校验, 跟数据绑定有区别.
-
表单model
绑定整个表单数据
-
表单rules
可以设定每个字段的校验规则
-
表单每一项对应的prop
设置在 el-form-item 上面
-
发送请求前的一次保底总校验
本来我们设定的 rules 规则都有触发条件, 但是我们在提交表单前,
希望无需触发器, 也想将整个表单全部验证一次
this.$refs.form.validate()
这个函数可以有两种可能的用法, 这是饿了么封装的时候已经做好处理的
- 传入回调函数作为参数, 这个回调会得到两个形参, 1. 是否通过校验的布尔值, 2. 没通过的项组成的对象
- 不传入回调, 自动变成一个 promise 返回, 成功逻辑写在 then 里面, 失败逻辑写在我们的catch 里面
components/user/loginForm.vue
的新增代码
// 其他代码...
data(){
return {
// 其他代码...
// 表单规则
rules: {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
},
],
},
}
},
// 其他代码...
使用el-form-item
添加prop
属性
<!-- 其他代码... -->
<!-- 新增了prop属性 -->
<el-form-item class="form-item" prop="username">
<el-input
placeholder="用户名/手机"
v-model="form.username">
</el-input>
</el-form-item>
<!-- 新增了prop属性 -->
<el-form-item class="form-item" prop="password">
<el-input
placeholder="密码"
type="password"
v-model="form.password">
</el-input>
</el-form-item>
<!-- 其他代码... -->
现在可以尝试在把
input
输入框的值清空,会出现在rules
中的定义的提示内容
登录接口
接下来要调用登录的接口进行登录了,如果一切正常的话,我们可以看到后台返回的用户信息,如果登录失败,我们需要对统一对错误的返回进行处理,这个我们在最后再统一实现。
现在只关注成功部分
修改components/user/loginForm.vue
的提交登录事件:
// 其他代码...
// 提交登录
methods: {
handleLoginSubmit(){
// 验证表单
this.$refs['form'].validate((valid) => {
// 为true表示没有错误
if (valid) {
this.$axios({
url: "/accounts/login",
method: "POST",
data: this.form
}).then(res => {
console.log(res.data);
})
}
})
}
}
// 其他代码...
现在登录接口可以开始访问了,服务器给我们提供了测试账号密码
账号:
13800138000
密码:
123456
如果正常登录应该可以在控制看到打印出来的用户信息,则表示登录成功了。
总结
- 在
components/user
中新建loginForm.vue
表单组件 - 使用
Element-ui
的表单组件绑定数据和验证表单 - 调用登录接口
表单验证拓展
-
怎么样使用正则表达式校验
-
校验顺序
- required 代表必填, 最优先的
- 如果没有 required 然后用户没有填数据, 那么默认校验通过, 其他的规则不生效
- 其他同级别的校验规则按照对象的先后顺序来
-
如何在输入框聚焦时, 暂时清除错误提示
使用store管理数据 (vuex)
什么是 vuex ?
-
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
-
它采用集中式存储管理应用的所有组件的状态,
-
并以相应的规则保证状态以一种可预测的方式发生变化。
(必须按照规定的方式改变数据)
面对一个储存状态数据的工具时, 有几个方面的问题我们马上可以想到的
- 数据放在哪里 store state 里面
- 如何存入/更新数据 可以在 store state 里面初始化 / 调用 mutation 函数进行数据改造
- 如何获取数据 this.$store.state.模块名.字段名
概念
在实际使用之前了解 vuex 三个概念
-
store 仓库
-
创建仓库, 因为 nuxt 框架已经封装好各种引入, 所以可以直接使用, 只需要创建文件即可
-
-
state 状态, 所有数据
如何在组件当中显示一个 state 数据
this.$store.state.模块名.字段名
-
mutation 规定对数据进行修改的方法
- 创建
export const mutations = { // mutations 是一个对象 // 里面的每一个属性都是一个函数 // 这里的函数专门用来修改这个仓库的数据 state setAbc(state, data) { // 所有 Mutation 函数都可以接受两个参数 // 第一是 state 对象本身 // 第二是外面调用时传入的数据 // 我们现在想修改 abc state.abc = data; } }
- 使用
// this.$store.state.user.abc = 321 直接复制是不行的 // 需要使用 commit 的方式调用 mutation // 调用时需要两个参数,第一个是mutaion 的路径,包括文件名和函数名,中间用反斜杠隔开 // 第二个是你想要传进去的 data 数据 setTimeout(() => { this.$store.commit('user/setAbc', 321); }, 1000);
优势
- js 原生的数据对象写法, 比起 localStorage 不需要做转换, 使用方便
- 属于 vue 生态一环, 能够触发响应式的渲染页面更新 (localStorage 就不会)
- 限定了一种可预测的方式改变数据j, 避免大项目中, 数据不小心的污染
其实如果跟 localStorage 对比的话
localStorage.getItem == this.$store.state.xxx
localStorage.setItem == this.$store.commit()
vuex管理用户信息步骤(store)
-
在
store
文件夹新建user.js
, 配置 state 和 mutation -
在登录页面中实现登录,并保存数据到
store
的state
中 使用 commit 方法 -
在头部组件中显示用户信息
新建状态文件
在store
文件夹新建user.js
,并添加以下代码
// 用户管理
export const state = () => ({
// 采用接口返回的数据结构
userInfo: {
token: "",
user: {
},
},
})
export const mutations = {
};
export const actions = {
};
登录成功将数据存入 vuex
顶部组件,展示用户信息
判断 token的存在则将用户数据显示出来
不断获取数据进行渲染, 其实跟我们之前的 localStorage.getItem(‘user’) 很像
展示用户信息
在头部组件展示store
中保存的用户数据。
在components/header.vue
中实现该功能:
<!-- 其他代码... -->
<!-- 如果用户存在则展示用户信息,用户数据来自store -->
<el-dropdown v-if="$store.state.user.userInfo.token">
<el-row type="flex" align="middle" class="el-dropdown-link">
<nuxt-link to="#">
<img :src="$axios.defaults.baseURL + $store.state.user.userInfo.user.defaultAvatar"/>
{
{$store.state.user.userInfo.user.nickname}}
</nuxt-link>
<i class="el-icon-caret-bottom el-icon--right"></i>
</el-row>
<!-- 其他代码... -->
</el-dropdown>
<!-- 其他代码... -->
模板中使用
$store.state.user.userInfo
可以访问store
的数据,虽然长了点,但是不难理解。注册功能也是同样的优化思路
保存store到本地 localStorage
现在用户已经保存到store
了,但是还有一个问题,数据是保存在变量缓存中的,如果页面一刷新,那么数据就会不见了,这样是不合理的。
所以我们需要使用localStorage
实现状态持久化,
- 概念就是每当 vuex 数据发生变化, 就存放到 localStorage 里面去
- 每当页面刷新的时候, localStorage 数据恢复到 vuex 里面
另外由于nuxtjs
是运行在服务器的,不能直接在store
中使用浏览器的localStorage方法,
所以我们需要依赖一些判断
但是不想自己写, 可以直接调用一个插件来实现以上的所有功能。
思路
监听数据变化, 每当登录完毕,
vuex 数据发生变化,就要将数据保存到浏览器 本地 (指用户浏览器localStorage)
页面打开时, 会尝试将之前保存过的数据恢复到 vuex 当中即可
有点复杂, 找个插件帮忙
实现缓存信息到本地
nuxtjs
中store
不能直接使用浏览器的lcoalStorage
方法,
而且自己写数据同步功能比较麻烦,
所以我们需要依赖一个插件vuex-persistedstate
,该插件会把本地存储的数据存储到store
。
- 安装插件
npm install --save vuex-persistedstate
-
需要创建一个 localStorage 自定义插件用来引入第三方包
以前我们引入第三方的插件时, 可以直接在 main.js 入口文件
的 new Vue 根实例创建之前, 添加代码,
nuxt 的机制是自定义插件,存放在 plugins 文件夹, 然后用配置进行引入
创建插件的两步
在根目录
plugins
中新建文件localStorage.js
,加入以下代码
import createPersistedState from 'vuex-persistedstate'
export default ({
store}) => {
window.onNuxtReady(() => {
createPersistedState({
key: "store", // 读取本地存储的数据到store
})(store)
})
}
3. 导入插件
修改nuxt.config.js
配置文件,在plugins
配置项中新增一条数据
// 其他代码...
plugins: [
// 其他代码...
{
src: '@/plugins/localStorage', ssr: false }
],
// 其他代码...
修改完后重新启动项目即可。
总结
使用vuex-persistedstate
保存 vuex 到本地存储
- 安装
- 创建插件文件 plugins/ 文件夹下面
- 在 nuxt.config.js 的 plugins 配置里面引入插件, 注意设置 ssr 为 false
- 重启项目即可