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

本文基于工作项目开发,做的整理笔记
前段时间公司有的小伙伴刚开始学习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)


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268