闲云旅游day02-首页,登录页

今日开发任务

  • 首页搜索跳转功能

    1. 搜索框布局
    2. 点击 tab 进行切换
  1. 点击搜索跳转页面
  • 登录表单布局

    登录页 tab 切换 登录/注册

    根据当前激活项, 显示 loginform / registerform

    利用 el-form 布局登录表单

  • 实现登录功能

    绑定数据

    校验输入

    发送请求

  • 掌握使用store管理用户登录数据 (vuex)

    • 初步简介 store state mutations
    • 实现 vuex 数据的初始化, 渲染和修改
    • 登录完毕将用户数据存放到 vuex , 头部组件判断数据进行显示
    • 利用插件 vuex-persistedstate 保持 vuex 数据持久化

首页搜索框和搜索跳转

思路

  1. 添加搜索框布局, 定位方式居中

  2. 搜索框tab切换执行不同的操作

    凡是有 tab 切换,第一时间想到有一个记录当前状态的变量

  3. 搜索跳转

实现步骤

1.搜索框布局

这里面的代码复制之前,要分辨那些需要

2.tab 高亮切换3个重点

  1. 有一个 data 变量储存当前激活状态 currentOption

  2. tab 增加点击事件, 改变 currentOption

  3. 动态给当前激活的 tab 添加 class

把搜索框定位在轮播图上,在pages/index.vuetemplate新增以下代码:

<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.vuescript替换如下:

<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.vuestyle替换如下:

<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);
    }
},
    
// 省略其他代码

搜索跳转

在输入时按下回车键,或者点击搜索放大镜图标, 触发搜索时候会跳转到当前tabpageUrl页面路径,并且在url上携带上输入框的值

页面会根据参数进行搜索请求

// 省略其他代码

// 搜索时候触发
handleSearch(){
    
    
    const item = this.options[this.currentOption];
    // 跳转时候给对应的页面url加上搜索内容参数
    this.$router.push(item.pageUrl + this.searchValue);
}
    
// 省略其他代码

总结

  1. 先把搜索框定位在轮播图上。 (css 的问题)

  2. tab添加切换效果,并且判断如果是机票tab,直接跳转到机票首页。

    最重要的一个变量是 currentOption

  3. 实现搜索跳转,注意跳转的链接来自当前选中的taburl属性,并且附带上参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hKxELs3l-1604902235993)(讲义.assets/image-20200506160250475.png)]

登录注册页布局

首先添加 切换的 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>

在预留的位置中将会导入登录组件和注册组件。

登录功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mYiUZoJ8-1604902236000)(讲义.assets/1561362542495-1588890287280.png)]

思路

  1. components/user中新建loginForm.vue表单组件

  2. 使用Element-ui的表单组件布局

  3. 表单数据绑定 v-model

  4. 表单验证

    输入框失去焦点,需要验证

    点击登录按钮, 发送登录请求前, 需要一次整个表格的验证

    使用饿了么封装好的验证方法

  5. 登录接口对接 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"/> -->

表单数据绑定

修改dataform数据,然后使用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. 传入回调函数作为参数, 这个回调会得到两个形参, 1. 是否通过校验的布尔值, 2. 没通过的项组成的对象
    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

如果正常登录应该可以在控制看到打印出来的用户信息,则表示登录成功了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odmiHUvV-1604902236002)(讲义.assets/image-20200303102101626.png)]

总结

  1. components/user中新建loginForm.vue表单组件
  2. 使用Element-ui的表单组件绑定数据和验证表单
  3. 调用登录接口

表单验证拓展

  1. 怎么样使用正则表达式校验

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6ZlZht5-1604902236008)(讲义.assets/image-20200508140825633.png)]

  2. 校验顺序

    • required 代表必填, 最优先的
    • 如果没有 required 然后用户没有填数据, 那么默认校验通过, 其他的规则不生效
    • 其他同级别的校验规则按照对象的先后顺序来
  3. 如何在输入框聚焦时, 暂时清除错误提示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNvKPnp7-1604902236012)(讲义.assets/image-20200508142226372.png)]

使用store管理数据 (vuex)

什么是 vuex ?

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

  • 它采用集中式存储管理应用的所有组件的状态,

  • 并以相应的规则保证状态以一种可预测的方式发生变化。

    (必须按照规定的方式改变数据)

面对一个储存状态数据的工具时, 有几个方面的问题我们马上可以想到的

  • 数据放在哪里 store state 里面
  • 如何存入/更新数据 可以在 store state 里面初始化 / 调用 mutation 函数进行数据改造
  • 如何获取数据 this.$store.state.模块名.字段名

概念

在实际使用之前了解 vuex 三个概念

  • store 仓库

    • 创建仓库, 因为 nuxt 框架已经封装好各种引入, 所以可以直接使用, 只需要创建文件即可

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u2IzrfmU-1604902236020)(讲义.assets/image-20201107120252930.png)]

  • state 状态, 所有数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j8avAWef-1604902236023)(讲义.assets/image-20201107120617849.png)]

    如何在组件当中显示一个 state 数据

    this.$store.state.模块名.字段名

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qyIejM9t-1604902236024)(讲义.assets/image-20201107121420564.png)]

  • mutation 规定对数据进行修改的方法

    1. 创建
    export const mutations = {
          
          
        // mutations 是一个对象
        // 里面的每一个属性都是一个函数
        // 这里的函数专门用来修改这个仓库的数据 state
        setAbc(state, data) {
          
          
            // 所有 Mutation 函数都可以接受两个参数
            // 第一是 state 对象本身
            // 第二是外面调用时传入的数据
            // 我们现在想修改 abc
            state.abc = data;
        }
    }
    
    1. 使用
        // 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)

  1. store文件夹新建user.js, 配置 state 和 mutation

  2. 在登录页面中实现登录,并保存数据到storestate中 使用 commit 方法

  3. 在头部组件中显示用户信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h1ZF4u23-1604902236026)(讲义.assets/image-20201107145527829.png)]

新建状态文件

store文件夹新建user.js,并添加以下代码

// 用户管理
export const state = () => ({
    
    
    // 采用接口返回的数据结构
    userInfo: {
    
    
        token: "",
        user: {
    
    },
    },
}) 

export const mutations = {
    
    };

export const actions = {
    
    };

登录成功将数据存入 vuex

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zofpv5RK-1604902236027)(讲义.assets/image-20200303144046010.png)]

顶部组件,展示用户信息

判断 token的存在则将用户数据显示出来

不断获取数据进行渲染, 其实跟我们之前的 localStorage.getItem(‘user’) 很像

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4CBSvuX-1604902236028)(讲义.assets/image-20200303145014710.png)]

展示用户信息

在头部组件展示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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8TmwNJLi-1604902236029)(讲义.assets/image-20201107151943516.png)]

现在用户已经保存到store了,但是还有一个问题,数据是保存在变量缓存中的,如果页面一刷新,那么数据就会不见了,这样是不合理的。

所以我们需要使用localStorage实现状态持久化

  • 概念就是每当 vuex 数据发生变化, 就存放到 localStorage 里面去
  • 每当页面刷新的时候, localStorage 数据恢复到 vuex 里面

另外由于nuxtjs是运行在服务器的,不能直接在store中使用浏览器的localStorage方法

所以我们需要依赖一些判断

但是不想自己写, 可以直接调用一个插件来实现以上的所有功能。

思路

监听数据变化, 每当登录完毕,

vuex 数据发生变化,就要将数据保存到浏览器 本地 (指用户浏览器localStorage)

页面打开时, 会尝试将之前保存过的数据恢复到 vuex 当中即可

有点复杂, 找个插件帮忙

实现缓存信息到本地

nuxtjsstore不能直接使用浏览器的lcoalStorage方法,

而且自己写数据同步功能比较麻烦,

所以我们需要依赖一个插件vuex-persistedstate,该插件会把本地存储的数据存储到store

插件地址:https://github.com/robinvdvleuten/vuex-persistedstate

  1. 安装插件
npm install --save vuex-persistedstate
  1. 需要创建一个 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 }
],
      
// 其他代码...

修改完后重新启动项目即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PLKzmg3x-1604902236031)(讲义.assets/image-20200303155917264.png)]

总结

使用vuex-persistedstate保存 vuex 到本地存储

  1. 安装
  2. 创建插件文件 plugins/ 文件夹下面
  3. 在 nuxt.config.js 的 plugins 配置里面引入插件, 注意设置 ssr 为 false
  4. 重启项目即可

猜你喜欢

转载自blog.csdn.net/weixin_48371382/article/details/109576593