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

今日开发任务

  • 首页搜索跳转功能

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

    登录页 tab 切换 登录/注册

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

    利用 el-form 布局登录表单

  • 实现登录功能

    绑定数据

    校验输入

    发送请求

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

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

首页搜索框和搜索跳转

思路

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

    扫描二维码关注公众号,回复: 12835682 查看本文章
  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