美文网首页vue
Vue教程--Wap端项目搭建从0到1(详解1)

Vue教程--Wap端项目搭建从0到1(详解1)

作者: SeasonDe | 来源:发表于2017-11-07 20:03 被阅读2625次

    本文基于工作项目开发,做的整理笔记
    前段时间公司有的小伙伴刚开始学习vue,就直接着手用在新项目上,以项目实战步步为营,不断推进vue的学习和使用。时间短,需求多,又是刚刚上手,遇到的坑和困难也真不少,感觉每天都在疯狂地解决问题。说真的,每种技术的学习和使用,在实际项目的开发上得到了充分检验,个人能力也在快速的成长。
    前一段时间,写过文章“Vue教程--使用官方脚手架构建实例”,主要是针对PC端,架构而写。当初的目的,也是想做为一个入门的教程,但是根据反馈和自己后面的感受,发现并不是很好,并没有做到真正的一步步上手。
    今天决定专门针对Wap端去做这样一个demo,整体架构的搭建,并含有一些通用的功能。其中,部分知识点请回看前面那篇文章。对比来看,此篇应该更为详细,步步为营。

    前提条件:
    你已经了解vue的基础知识,尝试过使用vue-cli官方脚手架搭建项目。

    编码环境:
    system:OS X EI Capitan 10.12.5
    npm:5.4.2
    node:v8.8.0
    vue-cli:@lastest

    相关技术栈:
    vue2 + vuex + vue-router + webpack + ES6/7 + fetch/axios + sass + flex + svg

    相关地址:
    项目代码github地址:https://github.com/YuxinChou/vue-wap-demo
    项目在线地址:http://www.knowing365.com
    (可用手机扫描下文中二维码,或用chrome浏览器模拟手机访问)
    参考项目:https://github.com/bailicangdu/vue2-elm

    WAP端项目搭建从0到1.jpg

    目录
    | - 0.传送门
    | - 1.安装
    | - 2.项目说明
    | - 3.项目搭建
      | - Step1. 初始化
      | - Step2. 母版页Layout
      | - Step3. 配置rem
      | - Step4. 配置sass
      | - Step5. 顶部导航header
      | - Step6. 引入iconfont
      | - Step7. 侧边菜单sidebar
      | - Step8. 底部导航footer
      | - Step9. 返回顶部backToTop(组件)
      | - Step10. 仓库存储store
      | - Step11. 侧边菜单状态保存
      | - Step12. 搜索栏searchBar(组件)
      | - Step13. 页面添加
      | - Step14. 弹窗提示(组件)
      | - ---------------------------------- 下内容为详解2
      | - Step15. 完善login页面(fetch请求数据)
      | - Step16. 合理引入svg
      | - Step17. 用axios实现请求(取代原生fetch)
      | - Step18. 登录状态存入仓库
      | - Step19. 滚动加载更多(组件)
      | - Step20. 回到指定位置(组件)
      | - Step21. 完善消息列表页面
      | - Step22. 顶部菜单改造(slot的使用)
      | - --------------------------------- 下内容为详解3
      | - Step23. 完善其他页面
      | - Step24. 权限检查
      | - Step25. 页面切换动画transition
      | - Step26. 轮播展示(swiper)
      | - Step26. 分享功能(vue-social-share)
      | - Step28. ...
    | - 4.项目部署
      | - a)本地部署
      | - b)服务器部署
    | - 5.后续

    0.传送门

    官网“起步”传送门:http://cn.vuejs.org/v2/guide/#起步

    1.安装

    你已经安装了npm,它是随node.js安装的,装了node.js也就有了它。
    node.js安装下载地址:http://nodejs.cn/download/

    # 检查node.js是否安装,若有则显示版本号
    $ node -v
    # 检查npm的版本号
    $ npm -v
    # 若要更新npm,使用
    $ npm install npm@latest -g
    

    一般,我们还会安装淘宝镜像cnpm,因为在墙内,有时候使用npm安装会很慢,所以需要。

    # 安装cnpm,并指定镜像地址
    $ npm install -g cnpm --registry=https://registry.npm.taobao.org
    

    写文章时,我的目前版本如下:

    Yuxin's MacBook Pro:Vue yuxin$ node -v
    v8.8.0
    Yuxin's MacBook Pro:Vue yuxin$ npm -v
    5.4.2
    Yuxin's MacBook Pro:Vue yuxin$ cnpm -v
    cnpm@4.5.0 (/usr/local/lib/node_modules/cnpm/parse_argv.js)
    npm@3.10.10 (/usr/local/lib/node_modules/cnpm/node_modules/npm/lib/npm.js)
    node@8.8.0 (/usr/local/bin/node)
    npminstall@2.29.1 (/usr/local/lib/node_modules/cnpm/node_modules/npminstall/lib/index.js)
    prefix=/usr/local 
    darwin x64 16.6.0 
    registry=https://registry.npm.taobao.org
    

    vue的安装:

    # 最新稳定版
    $ npm install vue
    # 它的命令行工具
    $ npm install --global vue-cli   
    

    我们接下来就使用它的命令行工具 vue-cli 来构建项目
    (类似的还有在react.js方面,快速上手会使用到‘蚂蚁金服’的Ant-design,使用antd-init或者dva-cli,都是命令行构建项目。)

    2.项目说明

    以【QQ手机APP】为UI界面参考,从母版页,列表页,左侧菜单,上下菜单,顶部导航,滚动加载,权限等页面及功能一一按步骤开发完成。那么这里的思路是:

    • 先搭建母版页Layout
    • 顶部导航header
    • 侧边菜单sidebar
    • 上下菜单navbar(扩展)
    • 底部导航footer
    • 页面添加
    • 功能(返回顶部,滚动加载...)
    • 接口请求
    • 仓库存储
    • 权限检查
    • ...

    整体的页面效果,大概如下:

    vue-wap-demo.jpg

    二维码访问,或者访问线上地址
    (注:未作PC兼容,请使用chrome手机模式访问)


    项目demo代码:GitHub地址


    3.项目搭建

    使用命令行工具,开始一点点构建项目:

    Step1. 初始化

    去到你的指定目录,初始化项目,名称为vue-wap-demo
    (注意:ESLint那里我选择了None模式,为了简单)

    # 去到放项目的文件夹地址
    Yuxin's MacBook Pro:Vue yuxin$ cd /Users/yuxin/Documents/Season/Project/Vue 
    # 创建基于webpack模版的项目,名称为vue-wap-demo
    Yuxin's MacBook Pro:Vue yuxin$ vue init webpack vue-wap-demo
    
    ? Project name vue-wap-demo  //直接回车(不修改的话)
    ? Project description A Vue.js project  //直接回车
    ? Author Season <yuxin0721@gmail.com>  //直接回车
    ? Vue build standalone //直接回车
    ? Install vue-router? Yes  //我选择了Yes
    ? Use ESLint to lint your code? Yes  //我选择了Yes
    ? Pick an ESLint preset none  //我选择了None模式!!!(注意:这里我用了None,为了简单)
    ? Setup unit tests with Karma + Mocha? No  //我选择了No(暂时不需要)
    ? Setup e2e tests with Nightwatch? No  //我选择了No(暂时不需要)
    
       vue-cli · Generated "vue-wap-demo".
    
       To get started:
       
         cd vue-wap-demo
         npm install
         npm run dev
       
       Documentation can be found at https://vuejs-templates.github.io/webpack
    
    

    执行上面提示中的命令,把项目运行起来

    # 进入项目文件夹
    Yuxin's MacBook Pro:Vue yuxin$ cd vue-wap-demo
    # 安装
    Yuxin's MacBook Pro:vue-wap-demo yuxin$ npm install
    
    > fsevents@1.1.2 install /Users/yuxin/Documents/Season/Project/Vue/vue-wap-demo/node_modules/fsevents
    > node install
    
    [fsevents] Success: "/Users/yuxin/Documents/Season/Project/Vue/vue-wap-demo/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
    Pass --update-binary to reinstall or --build-from-source to recompile
    
    > uglifyjs-webpack-plugin@0.4.6 postinstall /Users/yuxin/Documents/Season/Project/Vue/vue-wap-demo/node_modules/uglifyjs-webpack-plugin
    > node lib/post_install.js
    
    npm notice created a lockfile as package-lock.json. You should commit this file.
    added 1059 packages in 26.516s
    
    # 运行
    Yuxin's MacBook Pro:vue-wap-demo yuxin$ npm run dev
    
    > vue-wap-demo@1.0.0 dev /Users/yuxin/Documents/Season/Project/Vue/vue-wap-demo
    > node build/dev-server.js
    
    > Starting dev server...
    
    
     DONE  Compiled successfully in 2324ms                                  14:38:51
    

    浏览器查看,效果如下:

    项目初始化效果.jpg

    此时,项目代码结构如下:

    # 当前代码结构
    vue-wap-demo
    ├── build                      // 构建相关  
    ├── config                     // 配置相关
    ├── node_modules               // 模块安装的文件夹
    ├── src                        // 核心代码
    │   ├── assets                 // 静态资源
    │   ├── components             // 组件
    │   ├── router                 // 路由
    │   ├── App.vue                // 入口页面
    │   └── main.js                // 入口 加载组件 初始化等
    ├── static                     // 第三方不打包资源
    ├── .babelrc                   // babel-loader 配置
    ├── .editorconfig              // 代码编辑 配置项
    ├── .eslintignore              // eslint 忽略项
    ├── .eslintrc.js               // eslint 配置项
    ├── .gitignore                 // git 忽略项
    ├── favicon.ico                // favicon图标
    ├── index.html                 // html模板
    ├── package-lock.json          // package-lock.json
    ├── package.json               // package.json
    └── �README.md                  // 说明文档
    
    # 注意!!!
    # 下面是教程结束时的代码结构(可忽视)
    vue-wap-demo
    ├── build                      // 构建相关  
    ├── config                     // 配置相关
    ├── dist                       // 打包的部署文件
    ├── node_modules               // 模块安装的文件夹
    ├── screenshots                // 项目截图
    ├── src                        // 核心代码
    │   ├── assets                 // 静态资源
    │   ├── components             // 组件
    │   ├── page                   // 页面
    │   ├── router                 // 路由
    │   ├── service                // 请求服务
    │   ├── store                  // 仓库存储
    │   ├── style                  // 样式
    │   ├── utils                  // 公用方法
    │   ├── App.vue                // 入口页面
    │   └── main.js                // 入口 加载组件 初始化等
    ├── static                     // 第三方不打包资源
    ├── .babelrc                   // babel-loader 配置
    ├── .editorconfig              // 代码编辑 配置项
    ├── .eslintignore              // eslint 忽略项
    ├── .eslintrc.js               // eslint 配置项
    ├── .gitignore                 // git 忽略项
    ├── favicon.ico                // favicon图标
    ├── index.html                 // html模板
    ├── package-lock.json          // package-lock.json
    ├── package.json               // package.json
    └── �README.md                  // 说明文档
    

    Step2. 母版页Layout

    初始化后,在是src/components路径下默认有一个HelloWorld.vue页面,我们将其改名为Layout.vue作为母版页,调整一下内容,如下:

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper">
        <!-- main -->
        <div class="main_wrapper">
          <h1 class="title">{{ msg }}</h1>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        data () {
          return {
            msg: 'Welcome to Your Vue.js App'
          }
        }
      }
    </script>
    
    <style scoped>
      /*layout*/
      /*此时rem还没引入(Step3)*/
      /*此时sass也还不能用(Step4)*/
      .app_wrapper {
        background-color: #f2f2f2;
      }
      .main_wrapper .title {
        font-size: 1rem;
        color: #f00;
      }
    </style>
    

    同时需要修改router/index.js的内容,把HelloWorld的相关引用改为Layout,如下:

    /**********************************************/
    /* src/router/index.js                        */
    /**********************************************/
    
    import Vue from 'vue'
    import Router from 'vue-router'
    import Layout from '@/components/Layout'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'Layout',
          component: Layout
        }
      ]
    })
    

    现在我们可以看见修改后的效果。(开发的时候,一直保持项目运行就可以了)

    Step3. 配置rem

    src文件夹下创建utils文件夹,并添加文件rem.js,代码如下:

    /**********************************************/
    /* src/utils/rem.js                           */
    /**********************************************/
    
    (function(doc, win) {
        var docEl = doc.documentElement,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
            recalc = function() {
                var clientWidth = docEl.clientWidth;
                if (!clientWidth) return;
                docEl.style.fontSize = 20 * (clientWidth / 320) + 'px';
            };
        if (!doc.addEventListener) return;
        win.addEventListener(resizeEvt, recalc, false);
        doc.addEventListener('DOMContentLoaded', recalc, false);
    })(document, window);
    
    // 注解:
    // 设计稿为320的时候,1rem=20px
    // 设计稿为640的时候,1rem=40px
    // 设计稿宽768的时候,1rem=48px
    

    rem.js引入到src\main.js中,即插入代码import './utils/rem',最终代码如下:

    /**********************************************/
    /* src/main.js                                */
    /**********************************************/
    
    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import './utils/rem'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      template: '<App/>',
      components: { App }
    })
    
    

    此时,再看运行后的效果,使用chrome手机模式预览,可以发现rem已经生效。

    Step4. 配置sass

    整个项目过程中我们将使用sass,但是目前还没有配置。我们先修改Layout.vue,将它的style改成sass写法,如下:

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    ...
    <style lang="scss" scoped>
      /*layout*/
      .app_wrapper {
        background-color: #f2f2f2;
        .main_wrapper {
          .title {
            font-size: 1rem;
            color: #f00;
          }
        }
      }
    </style>
    
    // 注解:
    // 仅修改style这里,其他代码不变
    // 注意添加lang="scss"
    

    保存修改之后,我们可以看到终端报错。根据终端显示的错误信息和安装提示,我们可以一步步解决这个问题。

    # 按提示安装sass-loader
    $ npm install sass-loader 横线横线save(简书双横线自动连起来了)
    # 按提示安装node-sass
    $ npm install node-sass 横线横线save(简书双横线自动连起来了)
    

    再启动运行项目,没有报错,sass已经可以正常使用了。那么我们可能会疑问,为什么安装缺失的模块之后就可以用了呢?答案就在build文件夹下的配置中,想研究一下的可以去看看其中的webpack.base.conf.jsutilsl.js两个文件。

    Step5. 顶部导航header

    src/components中创建header文件夹,并创建head.vue,其代码如下:

    /**********************************************/
    /* src/components/header/head.vue             */
    /**********************************************/
    
    <template>
        <header id="head" class='header'>
          <span class="head_toggle" @click="toggleSideBar"><i class="iconfont icon-category"></i></span>
          <span class="head_logo">logo</span>
        </header>
    </template>
    
    <script>
      export default {
        data () {
          return {
            sidebar: false,
          }
        },
        methods: {
          toggleSideBar() {
            this.sidebar = !this.sidebar;
            console.log("sidebar  current value: " + this.sidebar);
          },
        },
      }
    </script>
    
    <style lang="scss" scoped>
      /*header*/
      .header {
        background-color: #3190e8;
        position: fixed;
        z-index: 10;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        height: 1.95rem;
    
        .head_toggle {
          position: absolute; 
          left:0.5rem; 
          i {
            line-height: 1.95rem;
            font-size: 1rem;
          }
        }
    
        .head_logo {
          line-height: 1.95rem;
          font-size: 1rem;
          color: #fff;
        }
      }
    </style>
    
    

    接着,我们在Layout.vue中引用head组件,代码如下:

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper">
        <!-- head -->
        <head-top></head-top>
        <!-- main -->
        <div class="main_wrapper">
          <img src="../assets/logo.png">
          <h1 class="title">{{ msg }}</h1>
        </div>
      </div>
    </template>
    
    <script>
      import headTop from './header/head'
      export default {
        components: {
          headTop
        },
        data () {
          return {
            msg: 'Welcome to Your Vue.js App'
          }
        },
      }
    </script>
    
    <style lang="scss" scoped>
      /*layout*/
      .app_wrapper {
        background-color: #f2f2f2;
        /*main_wrapper*/
        .main_wrapper {
          padding-top: 1.95rem;
          .title {
            font-size: 1rem;
            color: #f00;
          }
        }
      }
    </style>
    

    这里需要注意一点,我用的是<head-top></head-top>,而非<header></header>,这是因为headerhtml的关键字,类似的命名都不能使用。

    其次,为什么在script中的引用是headTop,而在html里的书写就变成了head-top。这个可以去查下,根据大写字母自动转变成-连接。

    Layout.vue中,我们还做了一件事,就是把src/App.vue中的logo图片那段代码暂时移动到这里了。同时,我们把src/App.vue中的style样式删除,下面将引入全站样式文件。此时,src/App.vue的代码如下:

    /**********************************************/
    /* src/App.vue                                */
    /**********************************************/
    
    <template>
      <div id="app">
        <router-view/>
      </div>
    </template>
    
    <script>
    export default {
      name: 'app'
    }
    </script>
    

    Step6. 引入iconfont

    上面我们说引入全站样式,在src下创建style文件夹,并依次添加样式入口文件index.scss,全站样式文件common.scss,字体样式文件iconfont.scss,mixin文件mixin.scss,如下:

    /**********************************************/
    /* src/style/index.scss                       */
    /**********************************************/
    
    @import "./common.scss";
    @import "./iconfont.scss";
    @import "./mixin.scss";
    
    /**********************************************/
    /* src/style/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:-webkit-autofill {
        -webkit-box-shadow: 0 0 0px 1000px #fff inset;
        -webkit-text-fill-color: #666;
    }
    
    
    /*定义placeholder提示的颜色*/
    
    ::-webkit-input-placeholder {
        /* WebKit browsers */
        // font-size: 0.6rem;
        color: #999;
    }
    
    :-moz-placeholder {
        /* Mozilla Firefox 4 to 18 */
        // font-size: 0.6rem;
        color: #999;
    }
    
    ::-moz-placeholder {
        /* Mozilla Firefox 19+ */
        // font-size: 0.6rem;
        color: #999;
    }
    
    :-ms-input-placeholder {
        /* Internet Explorer 10+ */
        // font-size: 0.6rem;
        color: #999;
    }
    
    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; //transparent;
        font-size: 0.6rem;
    }
    
    .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;
    }
    
    /**********************************************/
    /* src/style/iconfont.scss                    */
    /**********************************************/
    
    // 暂时没有内容
    
    /**********************************************/
    /* src/style/mixin.scss                       */
    /**********************************************/
    
    // 暂时没有内容
    

    既然已经创建了文件,我们就可以将样式用于项目,在src/main.js中引入index.scss,代码如下:

    /**********************************************/
    /* src/main.js                                */
    /**********************************************/
    
    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import './config/rem'
    import './style/index.scss';       //仅插入这句
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      template: '<App/>',
      components: { App }
    })
    

    此时,我们的iconfont.scss文件其实已经被引用到项目中了,但是因为没有字体样式的内容,没有效果。

    我们使用iconfont-阿里巴巴矢量图标库来为项目创建字体。登陆帐号后,添加字体,创建项目,下载项目字体,我们就可以拿来使用了。下载的字体文件包内容如下:

    # 下载的字体文件包内容
    font package
    ├── demo_fontclass.html             // class使用方式(demo)  
    ├── demo_symbol.html                // symbol使用方式(demo)  
    ├── demo_unicode.html               // unicode使用方式(demo)  
    ├── demo.css                        // 样式文件(demo)  
    ├── iconfont.css                    // 字体样式
    ├── iconfont.eot                    // 字体文件
    ├── iconfont.js                     // 字体脚本
    ├── iconfont.svg                    // 字体文件
    ├── iconfont.ttf                    // 字体文件
    └── iconfont.woff                   // 字体文件
    

    当然,你也可以选择使用字体在线路径进行引用,不过我一般没有这么做。

    src/assets中创建iconfont文件夹,将iconfont.eoticonfont.svgiconfont.ttficonfont.woff放入其中,接着修改iconfont.scss文件,内容如下:

    /**********************************************/
    /* src/style/iconfont.scss                    */
    /**********************************************/
    @font-face {
        font-family: "iconfont";
        src: url('../assets/iconfont/iconfont.eot?t=1509583688642');
        /* IE9*/
        src: url('../assets/iconfont/iconfont.eot?t=1509583688642#iefix') format('embedded-opentype'), /* IE6-IE8 */
        url('../assets/iconfont/iconfont.woff?t=1509583688642') format('woff'), /* chrome, firefox */
        url('../assets/iconfont/iconfont.ttf?t=1509583688642') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
        url('../assets/iconfont/iconfont.svg?t=1509583688642#iconfont') format('svg');
        /* iOS 4.1- */
    }
    
    .iconfont {
        font-family: "iconfont" !important;
        font-size: 0.8rem;
        font-style: normal;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
    }
    
    .icon-category:before {
        content: "\e699";
    }
    
    .icon-fanhui:before {
        content: "\e6e3";
    }
    
    .icon-tianjia:before {
        content: "\e6a5";
    }
    
    .icon-tianjia-circle:before {
        content: "\e68e";
    }
    
    .icon-xiaoxi:before {
        content: "\e672";
    }
    
    .icon-lianxiren:before {
        content: "\e63d";
    }
    
    .icon-dongtai:before {
        content: "\e602";
    }
    
    .icon-404:before {
        content: "\e60c";
    }
    
    .icon-search:before {
        content: "\e620";
    }
    
    .icon-vip:before {
        content: "\e603";
    }
    
    .icon-wallet:before {
        content: "\e625";
    }
    
    .icon-grab:before {
        content: "\e604";
    }
    
    .icon-favor:before {
        content: "\e61f";
    }
    
    .icon-photo:before {
        content: "\e63f";
    }
    
    .icon-file:before {
        content: "\e621";
    }
    
    .icon-setting:before {
        content: "\e8ea";
    }
    
    .icon-night:before {
        content: "\e653";
    }
    
    .icon-profile:before {
        content: "\e631";
    }
    
    .icon-phone:before {
        content: "\e626";
    }
    
    .icon-totop:before {
        content: "\e600";
    }
    
    .icon-QQ:before {
        content: "\e647";
    }
    
    .icon-arrow-down:before {
        content: "\e6a6";
    }
    
    .icon-arrow-right:before {
        content: "\e6a7";
    }
    
    

    注意:这里的代码和字体文件,已经包含整个项目所用到的全部字体。

    此时,我们看运行后的项目效果,headertoggleButton按钮已经显示字体。我们又发现不对了,这里是字体按钮,可是在线demo和效果截图中,这个按钮是一个头像图片,这个下一步我们再替换下。

    Step7. 侧边菜单sidebar

    src/components中创建sidebar文件夹,并创建sidebar.vue,其代码如下:

    /**********************************************/
    /* src/components/sidebar/sidebar.vue         */
    /**********************************************/
    <template>
        <div class="sidebar">
        <div class="top">
          <div class="top_info">
            <img class="top_image" src="https://cn.bing.com/az/hprichbg/rb/GreatSaltLake_ZH-CN12553220159_1920x1080.jpg"/>
            <span class="top_name">Season</span>
            <p class="top_sign">太多曾沾沾自喜誓必珍惜的情谊,败给了时光的腐朽</p>
          </div>
        </div>
        <div class="menu">
          <ul>
            <li>
              <i class="iconfont icon-vip"></i>我的超级会员
            </li>
            <li>
              <i class="iconfont icon-wallet"></i>QQ钱包
            </li>
            <li>
              <i class="iconfont icon-grab"></i>个性装扮
            </li>
            <li>
              <i class="iconfont icon-favor"></i>我的收藏
            </li>
            <li>
              <i class="iconfont icon-photo"></i>我的相册
            </li>
            <li>
              <i class="iconfont icon-file"></i>我的文件
            </li>
          </ul>
        </div>
        <div class="tool">
          <span @click="handleSidebar('setting')">
            <i class="iconfont icon-setting"></i>设置
          </span>
          <span @click="handleSidebar('login')">
            <i class="iconfont icon-night"></i>夜间
          </span>
        </div>
      </div>
    </template>
    
    <script>
      export default {
        methods: {
          handleSidebar(name) {
            // 暂时不要管这里,后面你可以留意下
            // 作用就是改变侧边菜单状态,并存储进仓库
            // this.$store.dispatch('ToggleSideBar');
    
            // 跳转对应页面
            this.$router.push({ path: '/'+name });
          }
        }
      }
    </script>
    
    <style lang="scss" scoped>
      /*sidebar*/
      .sidebar {
        position: fixed; 
        top: 0;
        bottom: 0;
        left: 0;
        height: 100%; 
        color: #999; 
        z-index: 110;
        background-color: #fff;
        i {
          margin-right: 0.5rem;
        }
        .top {
          position: relative;
          width: 100%;
          height: 30%;
          background-color: #333; 
          background-image: url(https://cn.bing.com/az/hprichbg/rb/CoastalBeech_ZH-CN8739604309_1920x1080.jpg);
          background-position: 50% 50%;
          background-size: cover;
          .top_info {
            position: absolute;
            left: 0;
            bottom: 0;
            padding: 0 0.8rem 0.6rem;
            width: 100%;
            .top_image {
              width: 1.8rem;
              height: 1.8rem;
              border-radius: 1.8rem;
              border: 0.12rem solid #fff;
            }
            .top_name {
              padding-left: 0.4rem;
              vertical-align: top;
              line-height: 2.2rem;
              font-size: 1.2rem;
              font-weight: 600;
              color: #fff;
            }
            .top_sign {
              padding: 0.2rem 0;
              font-size: 0.6rem;
              color: #fff;
              overflow: hidden;
              text-overflow: ellipsis;
              white-space: nowrap;
            }
          }
        }
        .menu {
          width: 100%;
          height: 70%;
          padding: 0.8rem 0 2rem 0;
          ul {
            padding: 0 0.8rem;
            li {
              height: 1.85rem;
              line-height: 1.85rem;
            }
          }
        }
        .tool {
          position: absolute;
          left: 0;
          bottom: 0;
          width: 100%;
          background-color: #fff;
          line-height: 2rem;
          span {
            display: inline-block;
            padding: 0 0.8rem;
            i {
              color: #666;
            }
          }
        }
      }
    </style>
    

    接着,我们在Layout.vue中引用sidebar组件,代码如下:

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper hideSidebar">
        <!-- head -->
        <head-top></head-top>
        <!-- sidebar -->
        <sidebar></sidebar>
        <!-- main -->
        <div class="main_wrapper">
          <img src="../assets/logo.png">
          <h1 class="title">{{ msg }}</h1>
        </div>
      </div>
    </template>
    
    <script>
      import headTop from './header/head'
      import sidebar from './sidebar/sidebar'
      export default {
        components: {
          headTop,
          sidebar
        },
        data () {
          return {
            msg: 'Welcome to Your Vue.js App'
          }
        },
      }
    </script>
    
    <style lang="scss">
      /*layout*/
      .app_wrapper {
        overflow-x: hidden;
        .header {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
        .sidebar {
          width: 13rem;
          transition: all .28s ease-out;
          transform: translate(0);
        }
        /*main_wrapper*/
        .main_wrapper {
          padding-top: 1.95rem;
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
    
        &.hideSidebar {
          .header {
            transform: translateX(0);
          }
          .sidebar {
            transform: translateX(-13rem);
          }
          .main_wrapper {
            transform: translateX(0);
          }
        }
      }
    </style>
    

    现在,我们把sidebar组件引入了,但是发现控制样式的类hideSidebar受到顶部导航header的变量sidebar控制,需要将变量放置在Layout.vue中,并且在侧边菜单显示的时候需要一个遮罩层masking

    src/components中创建masking文件夹,并创建masking.vue,其代码如下:

    /**********************************************/
    /* src/components/masking/masking.vue         */
    /**********************************************/
    
    <template>
      <div class="masking" @click="toggleSideBar"></div>
    </template>
    
    <script>
      export default {
        methods: {
          toggleSideBar() {
            //暂时先注释,后面store之后那步再打开
            // this.$store.dispatch('ToggleSideBar');
          },
        },
      }
    </script>
    
    <style lang="scss" scoped>
      /*masking*/
      .masking {
        display: block;
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 100;
      }
    </style>
    

    修改Layout.vue,引入masking组件,并添加sidebar变量及toggleSideBar方法,稍后header组件调用。

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper" :class="{hideSidebar:sidebar}">
        <!-- head -->
        <head-top @toggleSideBar="toggleSideBar"></head-top>
        <!-- sidebar -->
        <sidebar></sidebar>
        <!-- masking -->
        <masking></masking>
        <!-- main -->
        <div class="main_wrapper">
          <img src="../assets/logo.png">
          <h1 class="title">{{ msg }}</h1>
        </div>
      </div>
    </template>
    
    <script>
      import headTop from './header/head'
      import sidebar from './sidebar/sidebar'
      import masking from './masking/masking'
      export default {
        components: {
          headTop,
          sidebar,
          masking
        },
        data() {
          return {
            sidebar: false,
            msg: 'Welcome to Your Vue.js App'
          }
        },
        methods: {
          toggleSideBar() {
            this.sidebar = !this.sidebar;
            console.log("sidebar current value: " + this.sidebar);
          },
        },
      }
    </script>
    
    <style lang="scss">
      /*layout*/
      .app_wrapper {
        overflow-x: hidden;
        .header {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
        .sidebar {
          width: 13rem;
          transition: all .28s ease-out;
          transform: translate(0);
        }
        /*main_wrapper*/
        .main_wrapper {
          padding-top: 1.95rem;
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
    
        &.hideSidebar {
          .header {
            transform: translateX(0);
          }
          .sidebar {
            transform: translateX(-13rem);
          }
          .masking {
            display: none;
          }
          .main_wrapper {
            transform: translateX(0);
          }
        }
      }
    </style>
    

    修改header.vue文件,代码如下:

    /**********************************************/
    /* src/components/header/head.vue             */
    /**********************************************/
    <template>
        <header class='header'>
          <span class="head_toggle" @click="toggleSideBar">
            <img class="top_image" src="https://cn.bing.com/az/hprichbg/rb/GreatSaltLake_ZH-CN12553220159_1920x1080.jpg"/>
          </span>
          <span class="head_text">LOGO</span>
        </header>
    </template>
    
    <script>
      export default {
        methods: {
          toggleSideBar() {
            // this.sidebar = !this.sidebar;
            // console.log("sidebar current value: " + this.sidebar);
            this.$emit('toggleSideBar');
          },
        },
      }
    </script>
    
    <style lang="scss" scoped>
      /*header*/
      .header {
        background-color: #3190e8;
        background: -webkit-linear-gradient(right top, #61b8f8 , #5e8bf7); /* Safari 5.1 - 6.0 */
        background: -o-linear-gradient(bottom left, #61b8f8, #5e8bf7); /* Opera 11.1 - 12.0 */
        background: -moz-linear-gradient(bottom left, #61b8f8, #5e8bf7); /* Firefox 3.6 - 15 */
        background: linear-gradient(to bottom left, #61b8f8 , #5e8bf7); /* 标准的语法 */
        position: fixed;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        height: 1.95rem;
        z-index: 10;
        /*头像菜单按钮*/
        .head_toggle {
          position: absolute; 
          left:0.5rem; 
          img {
            width: 1.5rem;
            height: 1.5rem;
            border-radius: 1rem;
            margin-top: 0.2rem;
          }
          i {
            line-height: 1.95rem;
            font-size: 1rem;
          }
        }
        /*文字*/
        .head_text {
          line-height: 1.95rem;
          font-size: 0.7rem;
          color: #fff;
          display: inline-block;
        }
      }
    </style>
    

    现在来看一下效果,我们要的功能已经实现了。

    Step8. 底部导航footer

    src/components中创建footer文件夹,并创建footer.vue,其代码如下:

    /**********************************************/
    /* src/components/footer/footer.vue             */
    /**********************************************/
    
    <template>
        <div class='footer'>
        <router-link to="/messages" class="footer_menu" :class="{active:(activeIndex==0)}">
          <i class="iconfont icon-xiaoxi"></i>
          <span>消息</span>
        </router-link>
        <router-link to="/contacts" class="footer_menu" :class="{active:(activeIndex==1)}">
          <i class="iconfont icon-lianxiren"></i>
          <span>联系人</span>
        </router-link>
        <router-link to="/dynamics" class="footer_menu" :class="{active:(activeIndex==2)}">
          <i class="iconfont icon-dongtai"></i>
          <span>动态</span>
        </router-link>
      </div>
    </template>
    
    <script>
      export default {
        props: ['activeIndex'],
      }
    </script>
    
    <style lang="scss" scoped>
        .footer {
        background-color: #fff;
        position: fixed;
        z-index: 10;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        height: 1.95rem;
        display: flex;
        box-shadow: 0 -0.03rem 0.05rem rgba(0, 0, 0, .1);
        .footer_menu {
          flex: 1;
          display: flex;
          text-align: center;
          flex-direction: column;
          align-items: center;
          .iconfont {
            display: block;
            font-size: 1rem;
            margin-top: 0.2rem;
            color: #666;
          }
          span {
            display: block;
            font-size: 0.4rem;
            color: #666;
          }
          &.active {
            .iconfont {
              color: #68b7f9;
            }
            span {
              color: #68b7f9;
            }
          }
        }
      }
    </style>
    

    接着,我们在Layout.vue中引用footer组件,代码如下:

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper" :class="{hideSidebar:sidebar}">
        <!-- head -->
        <head-top @toggleSideBar="toggleSideBar"></head-top>
        <!-- sidebar -->
        <sidebar></sidebar>
        <!-- masking -->
        <masking></masking>
        <!-- footer -->
        <foot-menu :activeIndex="0"></foot-menu>
        <!-- main -->
        <div class="main_wrapper">
          <img src="../assets/logo.png">
          <h1 class="title">{{ msg }}</h1>
        </div>
      </div>
    </template>
    
    <script>
      import headTop from './header/head'
      import sidebar from './sidebar/sidebar'
      import masking from './masking/masking'
      import footMenu from '@/components/footer/footer'  
      export default {
        components: {
          headTop,
          sidebar,
          masking,
          footMenu
        },
        data () {
          return {
            sidebar: false,
            msg: 'Welcome to Your Vue.js App'
          }
        },
        methods: {
          toggleSideBar() {
            this.sidebar = !this.sidebar;
            console.log("sidebar current value: " + this.sidebar);
          },
        },
      }
    </script>
    
    <style lang="scss">
      /*layout*/
      .app_wrapper {
        overflow-x: hidden;
        .header {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
        .sidebar {
          width: 13rem;
          transition: all .28s ease-out;
          transform: translate(0);
        }
        /*main_wrapper*/
        .main_wrapper {
          padding-top: 1.95rem;
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
       .footer {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
    
        &.hideSidebar {
          .header {
            transform: translateX(0);
          }
          .sidebar {
            transform: translateX(-13rem);
          }
          .masking {
            display: none;
          }
          .main_wrapper {
            transform: translateX(0);
          }
          .footer {
            transform: translateX(0);
          }
        }
      }
    </style>
    

    那么做到这里,我们就已经能够将一个APP的基本结构用组件方式拼装起来,这个母版页将可以用在多个页面。我们已经掌握了这部分技能。

    但是,现在我们再回去看一下需要实现的demo,我们发现并不是所有页面都有footer,每个页面的header也不大相同。我们未来需要调整母版页Layout的代码,把顶部导航和底部导航丢到各个具体页面上。

    Step9. 返回顶部backToTop(组件)

    返回顶部也是一个常用的功能,看一下实现。在src/components中创建common文件夹,并创建backToTop.vue,其代码如下:

    /**********************************************/
    /* src/components/common/backToTop.vue        */
    /**********************************************/
    
    <template>
      <div v-show="visible" id="back_top" @click="backTop">
        <i class="iconfont icon-totop"></i>
      </div>
    </template>
    <script>
      export default {
        props: {
          scrollHeight: {
            type: Number,
            default: 100
          },
          timeSpan: {
            type: Number,
            default: 200
          }
        },
        data() {
          return {
            wHeight: 0,
            visible: false,
            ret: 0,
            obj: null,
            speed: 0,
            times: 0,
            time: 0,
          }
        },
        mounted () {
          window.addEventListener('scroll',this.hasScroll);
        },
        methods: {
          hasScroll(){
            const scrollTop = this.getScroll(window);
            this.visible = scrollTop > this.scrollHeight;
          },
          getScroll(w){
            this.ret = w.pageYOffset
            const method = 'scrollTop'
            if(typeof this.ret !== 'number'){
              let d= w.document;
              this.ret = d.documentElemelnt[method]
              if(typeof this.ret !== 'number'){
                this.ret = d.body[method]
              }
            }
            return this.ret
          },
          backTop(){
            const initerval = 30
            let num = this.timeSpan/initerval
            this.time = 0
            this.times = num;
            this.speed = this.ret / num
            this.obj = setInterval(this.setScroll,initerval)
          },
          setScroll(){
            if(this.time > this.times || this.ret<=0){
              clearInterval(this.obj)
              return
            }
            this.time++
            this.ret -= this.speed
            if(this.ret<0) {
              this.ret = 0;
            }
            document.documentElement.scrollTop = document.body.scrollTop = this.ret
          }
        },
      }
    </script>
    
    <style lang="scss" scoped>
      #back_top {
        position: fixed; 
        bottom: 10%; 
        right: 1rem;
        width: 1.6rem;
        height: 1.6rem;
        border-radius: 1.6rem;
        line-height: 1.6rem;
        text-align: center;
        background-color: rgba(49, 49, 49, 0.23);
        i {
          color: #666;
        }
      }
    </style>
    

    headerfooter一样,在Layout.vue中引用就好了,这里不单独贴代码了。

    Step10. 仓库存储store

    为了后面能把headerfooter移出Layout,放入每个单独的页面,我们需要把控制侧边菜单状态的变量存放在仓库store中管理。下面看看如何实现。

    src中创建store文件夹,并创建以下目录及文件:

    store
    ├── modules                       // 仓库模块
    │     ├── app.js                  // 基础模块(存放sidebar状态等)
    │     ├── common.js               // 页面模块(存放页面数据,晚点用到)
    │     └── user.js                 // 用户模块(存放用户信息,晚点用到)
    ├── getters.js                    // 计算属性
    └── index.js                      // 入口文件
    

    其中,index.jsgetters.jsmodules/app.js的代码如下:

    /**********************************************/
    /* src/store/index.js                         */
    /**********************************************/
    
    import Vue from 'vue';
    import Vuex from 'vuex';
    import app from './modules/app';
    import getters from './getters';
    
    Vue.use(Vuex);
    
    const store = new Vuex.Store({
        modules: {
            app
        },
        getters
    });
    
    export default store
    
    /**********************************************/
    /* src/store/getter.js                        */
    /**********************************************/
    
    const getters = {
        sidebar: state => state.app.sidebar
    };
    
    export default getters
    
    /**********************************************/
    /* src/store/modules/app.js                   */
    /**********************************************/
    
    import Cookies from 'js-cookie';
    
    const app = {
        state: {
            sidebar: !+Cookies.get('sidebarStatus')
        },
        mutations: {
            TOGGLE_SIDEBAR: state => {
                if (state.sidebar) {
                    Cookies.set('sidebarStatus', 1);
                } else {
                    Cookies.set('sidebarStatus', 0);
                }
                state.sidebar = !state.sidebar;
            },
        },
        actions: {
            ToggleSideBar: ({ commit }) => {
                commit('TOGGLE_SIDEBAR')
            },
        }
    };
    
    export default app;
    

    modules/app.js中,我们用了一个模块js-cookie,需要安装一下,如下:

    $ npm install js-cookier --save
    

    安装好了即可。

    接着我们在main.js中引入store,代码如下:

    /**********************************************/
    /* src/main.js                                */
    /**********************************************/
    
    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import './config/rem'
    import './style/index.scss';
    import store from './store/index'     // 插入这句
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      store,              // 插入这句
      template: '<App/>',
      components: { App }
    })
    

    此时仓库存储就搭建好了。

    Step11. 侧边菜单状态保存

    先修改Layout,将sidebar变量改成从仓库中获取;再修改header,调用仓库的方法改变sidebar的值。

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper" :class="{hideSidebar:sidebar}">
        <!-- head -->
        <head-top @toggleSideBar="toggleSideBar"></head-top>
        <!-- sidebar -->
        <sidebar></sidebar>
        <!-- masking -->
        <masking></masking>
        <!-- footer -->
        <foot-menu :activeIndex="0"></foot-menu>
        <!-- main -->
        <div class="main_wrapper">
          <img src="../assets/logo.png">
          <h1 class="title">{{ msg }}</h1>
        </div>
        <!-- backToTop -->
        <back-to-top></back-to-top>
      </div>
    </template>
    
    <script>
      import { mapGetters } from 'vuex'
      import headTop from './header/head'
      import sidebar from './sidebar/sidebar'
      import masking from './masking/masking'
      import footMenu from './footer/footer'  
      import backToTop from './common/backToTop'
      export default {
        components: {
          headTop,
          sidebar,
          masking,
          footMenu,
          backToTop
        },
        data () {
          return {
            sidebar: false,
            msg: 'Welcome to Your Vue.js App'
          }
        },
        computed: {
          ...mapGetters([
            'sidebar',
          ])
        },
      }
    </script>
    
    <style lang="scss">
      /*layout*/
      .app_wrapper {
        overflow-x: hidden;
        .header {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
        .sidebar {
          width: 13rem;
          transition: all .28s ease-out;
          transform: translate(0);
        }
        /*main_wrapper*/
        .main_wrapper {
          padding-top: 1.95rem;
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
       .footer {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
    
        &.hideSidebar {
          .header {
            transform: translateX(0);
          }
          .sidebar {
            transform: translateX(-13rem);
          }
          .masking {
            display: none;
          }
          .main_wrapper {
            transform: translateX(0);
          }
          .footer {
            transform: translateX(0);
          }
        }
      }
    </style>
    
    /**********************************************/
    /* src/components/header/head.vue                  */
    /**********************************************/
    
    <template>
        <header class='header'>
          <span class="head_toggle" @click="toggleSideBar">
            <img class="top_image" src="https://cn.bing.com/az/hprichbg/rb/GreatSaltLake_ZH-CN12553220159_1920x1080.jpg"/>
          </span>
          <span class="head_text">LOGO</span>
        </header>
    </template>
    
    <script>
      export default {
        methods: {
          toggleSideBar() {
            this.$store.dispatch('ToggleSideBar');
          },
        },
      }
    </script>
    
    <style lang="scss" scoped>
      /*header*/
      .header {
        background-color: #3190e8;
        background: -webkit-linear-gradient(right top, #61b8f8 , #5e8bf7); /* Safari 5.1 - 6.0 */
        background: -o-linear-gradient(bottom left, #61b8f8, #5e8bf7); /* Opera 11.1 - 12.0 */
        background: -moz-linear-gradient(bottom left, #61b8f8, #5e8bf7); /* Firefox 3.6 - 15 */
        background: linear-gradient(to bottom left, #61b8f8 , #5e8bf7); /* 标准的语法 */
        position: fixed;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        height: 1.95rem;
        z-index: 10;
        /*头像菜单按钮*/
        .head_toggle {
          position: absolute; 
          left:0.5rem; 
          img {
            width: 1.5rem;
            height: 1.5rem;
            border-radius: 1rem;
            margin-top: 0.2rem;
          }
          i {
            line-height: 1.95rem;
            font-size: 1rem;
          }
        }
        /*文字*/
        .head_text {
          line-height: 1.95rem;
          font-size: 0.7rem;
          color: #fff;
          display: inline-block;
        }
      }
    </style>
    

    此时,我们查看效果,这一块已经完成。

    Step12. 搜索栏searchBar(组件)

    查看我们要做的demo页面,发现很多页面有一个公共组件searchBar,我们先把这个处理一下。

    src/components/common中添加searchBar.vue,代码如下:

    /**********************************************/
    /* src/components/common/searchBar.vue        */
    /**********************************************/
    
    <template>
      <div class="search" @click="handleClick">
        <div class="search_content">
          <i class="iconfont icon-search"></i>&nbsp;搜索
        </div>
      </div>
    </template>
    <script>
      export default {
        methods: {
          handleClick() {
            this.$router.push({ path: '/search' });
          }
        }
      }
    </script>
    
    <style lang="scss" scoped>
      .search {
        padding: 0.5rem;
        background-color: #fff;
        .search_content {
          text-align: center;
          font-size: 0.5rem;
          line-height: 2;
          border-radius: 0.1rem;
          color: #999;
          background-color: #eee;
          i {
            font-size: 0.5rem;
            color: #999;
          }
        }
      }
    </style>
    

    代码中有一个事件,它的作用就是跳转到搜索页面,那么稍后我们添加这个页面,并添加相关路由。

    Step13. 页面添加

    现在我们先整理一下Layout.vue,把headerfooter移出去,等下将他们放在各个页面中。这里,我们还把页面的实际内容代码,用<router-view></router-view>替换,这个位置将渲染页面的具体内容。整理后的Layout.vue,代码如下:

    /**********************************************/
    /* src/components/Layout.vue                  */
    /**********************************************/
    
    <template>
      <div class="app_wrapper" :class="{hideSidebar:sidebar}">
        <!-- main -->
        <router-view></router-view>
        <!-- sidebar -->
        <sidebar></sidebar>
        <!-- masking -->
        <masking></masking>
        <!-- backToTop -->
        <back-to-top></back-to-top>
      </div>
    </template>
    
    <script>
      import { mapGetters } from 'vuex'
      import sidebar from './sidebar/sidebar'
      import masking from './masking/masking'
      import backToTop from './common/backToTop'
      
      export default {
        components: {
          sidebar,
          masking,
          backToTop,
        },
        
        computed: {
          ...mapGetters([
            'sidebar',
          ])
        },
      }
    </script>
    
    <style lang="scss">
      /*layout*/
      .app_wrapper {
        overflow-x: hidden;
        .header {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
        .sidebar {
          width: 13rem;
          transition: all .28s ease-out;
          transform: translate(0);
        }
        /*main_wrapper*/
        .main_wrapper {
          padding-top: 1.95rem;
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
        .footer {
          transition: all .28s ease-out;
          transform: translateX(13rem);
        }
    
        &.hideSidebar {
          .header {
            transform: translateX(0);
          }
          .sidebar {
            transform: translateX(-13rem);
          }
          .masking {
            display: none;
          }
          .main_wrapper {
            transform: translateX(0);
          }
          .footer {
            transform: translateX(0);
          }
        }
      }
    </style>
    
    

    src中创建page文件夹,并创建以下文件:

    page
    ├── messages
    │     └── messages.vue             // 消息页面
    ├── contacts
    │     └── contacts.vue             // 联系人页面
    ├── dynamics
    │     └── dynamics.vue             // 动态页面
    ├── search
    │     └── search.vue               // 搜索页面
    ├── login
    │     └── login.vue                // 登陆页面
    ├── error
    │     └── pageNotFound.vue         // 404页面
    ├── ...
    ├── ...
    └── 其他请看源码
    

    所有页面的代码先如下:

    /**********************************************/
    /* 上述新创建页面                               */
    /**********************************************/
    
    <template>
      <div>
         这里是<对应页面名称>页面(区分一下)
      </div>
    </template>
    <script>
      export default {
      }
    </script>
    <style lang="scss" scoped>
    </style>
    

    上面代码,表示每个页面只是带有一句描述的空页面。下面,我们把路由补全,修改src/router/index.js,代码如下:

    /**********************************************/
    /* src/router/index.js                        */
    /**********************************************/
    
    import Vue from 'vue'
    import Router from 'vue-router'
    import Layout from '@/components/Layout'
    
    const Login = r => require.ensure([], () => r(require('../page/login/login')), 'Login')
    
    const Messages = r => require.ensure([], () => r(require('../page/messages/messages')), 'messages')
    const Contacts = r => require.ensure([], () => r(require('../page/contacts/contacts')), 'contacts')
    const Dynamics = r => require.ensure([], () => r(require('../page/dynamics/dynamics')), 'dynamics')
    const Search = r => require.ensure([], () => r(require('../page/search/search')), 'search')
    
    const PageNotFound = r => require.ensure([], () => r(require('../page/error/pageNotFound')), 'pageNotFound')
    
    Vue.use(Router)
    
    export default new Router({
        mode: "history",
        routes: [
            { path: '/login', name: 'Login', component: Login },
            {
                path: '/',
                component: Layout,
                children: [
                    { path: '', redirect: '/login' },
                    { path: '/messages', name: 'Messages', component: Messages },
                    { path: '/contacts', name: 'Contacts', component: Contacts },
                    { path: '/dynamics', name: 'Dynamics', component: Dynamics },
                    { path: '/search', name: 'Search', component: Search }
                ]
            },
            { path: '*', component: PageNotFound }
    
        ]
    })
    

    下面,我们通过修改URL,看一下是否可以访问所有页面。

    Step14. 弹窗提示(组件)

    考虑到要用到“提示”,我们先添加一个这样的组件。在src/components/common中添加alertTip.vue,代码如下:

    /**********************************************/
    /* src/components/common/alertTip.vue         */
    /**********************************************/
    
     <template>
        <div class="alet_container">
            <section class="tip_text_container">
                <div class="tip_icon">
                    <span></span>
                    <span></span>
                </div>
                <p class="tip_text">{{alertText}}</p>
                <div class="confrim" @click="closeTip">确认</div>
            </section>
        </div>
    </template>
    
    <script>
        export default {
            data(){
              return{
                  positionY: 0,
                  timer: null,
              }
            },
            mounted(){
          
            },
            props: ['alertText'],
            methods: {
                closeTip(){
                    this.$emit('closeTip')
                }
            }
        }
    </script>
    
    <style lang="scss" scoped>
        @keyframes tipMove{
           0%   { transform: scale(1) }
           35%  { transform: scale(.8) }
           70%  { transform: scale(1.1) }
           100% { transform: scale(1) }
        }
        .alet_container{
            position: fixed;
            top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          z-index: 200;
          background-color: rgba(0,0,0,0.5);
        }
        .tip_text_container{
            position: absolute;
            top: 50%;
            left: 50%;
            margin-top: -6rem;
            margin-left: -6rem;
            width: 12rem;
            animation: tipMove .4s ;
            background-color: rgba(255,255,255,1);
            border: 1px;
            padding-top: .6rem;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            border: 1px;
            border-radius: 0.25rem;
            .tip_icon{
                width: 3rem;
                height: 3rem;
                border: 0.15rem solid #f8cb86;
                border-radius: 50%;
                display: flex;
                justify-content: center;
                align-items: center;
                flex-direction: column;
                span:nth-of-type(1){
                    width: 0.12rem;
                    height: 1.5rem;
                    background-color: #f8cb86;
                }
                span:nth-of-type(2){
                    width: 0.2rem;
                    height: 0.2rem;
                    border: 1px;
                    border-radius: 50%;
                    margin-top: .2rem;
                    background-color: #f8cb86;
                }
            }
            .tip_text{
                font-size: 0.7rem;
            color: #333;
                line-height: .9rem;
                text-align: center;
                margin-top: .8rem;
                padding: 0 .4rem;
            }
            .confrim{
                font-size: 0.8rem;
            color: #fff;
                font-weight: bold;
                margin-top: .8rem;
                background-color: #4cd964;
                width: 100%;
                text-align: center;
                line-height: 1.8rem;
                border: 1px;
                border-bottom-left-radius: 0.25rem;
                border-bottom-right-radius: 0.25rem;
            }
        }
    </style>
    

    之后login页面请求的时候,我们再看看是如何使用的。

    由于文章篇幅限制,请继续阅读Vue教程--Wap端项目搭建从0到1(详解2)


    学习是一条漫漫长路,每天不求一大步,进步一点点就是好的。

    相关文章

      网友评论

        本文标题:Vue教程--Wap端项目搭建从0到1(详解1)

        本文链接:https://www.haomeiwen.com/subject/owkauxtx.html