Node+Vue实战项目

一、Node+Vue实战项目

1.1 创建Node项目、Vue项目

mkdir classweb
cd classweb/

express server

vue init webpack vueclient
.
|-- server
|   |-- app.js
|   |-- bin
|   |-- node_modules
|   |-- package-lock.json
|   |-- package.json
|   |-- public
|   |-- routes
|   `-- views
|-- tree.txt
`-- vueclient
    |-- README.md
    |-- build
    |-- config
    |-- index.html
    |-- node_modules
    |-- package.json
    |-- src
    `-- static

12 directories, 7 files

1.2 安装mongodb操作软件 Robomongo

create database 输入创建 classweb数据库
展开classweb,然后在collections右键, create collection 创建一个user表
user右键 insert document,然后输入后面的数据 ,save, (数据用户名 admin 密码是 123456 加密后的字段 还有手机号)

    "name" : "admin",
    "phone" : "13388868886",
    "password" : "4QrcOUm6Wau+VuBX8g+IPg=="








1.3 实现登录功能

// App.vue

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

componets文件夹中新建 login.vue

// Login.vue

<template>
  <div class="backlogin">
    <div class="login_box">
      <div class="title">后台登录</div>
      <div>
        <input type="text" placeholder="手机号/用户名" v-model="username" class="myinput" />
      </div>
      <div>
        <input type="password" placeholder="口令" v-model="password" class="myinput" />
      </div>
      <div class="login_other">
        <a href="javascript:;">找回密码</a>
        <input type="checkbox" id="rememberme" /><label for="rememberme">记住我</label>
      </div>
      <button :disabled="disablebtn" class="login">登录</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      username: "admin", /* 先预存测试值,以免手动输入 */
      password: "123456",
      disablebtn: false
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .login_box {
    width: 320px;
    margin: 50px auto;
  }
  .login_box .title {
    color: #273444;
    font-size: 1.5em;
    text-align: center;
    margin: 0 0 20px 0;
  }
  .login_box .myinput {
    width: 100%;
    border: 1px solid #cad3d3;
    height: 40px;
    line-height: 40px;
    margin: 5px 0 10px;
    border-radius: 3px;
    padding: 0 10px;
    outline: none;
    box-sizing: border-box;
  }
  .login_box .myinput:focus {
    border: 1px solid #4289dc;
  }
  .login_other {
    overflow: hidden;
  }
  .login_other a {
    float: right;
    color: #727f8f;
  }
  .login_other a:hover {
    color: #273444;
  }
  .login_other input, .login_other label {
    float: left;
    color: #727f8f;
  }
  .login_other input {
    margin: 4px 5px 0 0;
  }
  .login {
    box-sizing: border-box;
    border: 0;
    height: 44px;
    line-height: 44px;
    width: 100%;
    background: #4187db;
    font-size: 16px;
    border-radius: 3px;
    margin-right: 40px;
    transition: all 0.5s ease;
    cursor: pointer;
    outline: none;
    color: #fff;
    margin-top: 15px;
  }
  .login:hover {
    background: #2668b5;
  }
  .login[disabled] {
    opacity: .8;
  }
  .login[disabled]:hover {
    background: #4187db;
  }
  @media only screen and (max-width: 768px) {
    .login_box {
      width: 280px;
      margin: 50px auto;
    }
  }
</style>

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login
    }
  ]
})

登录功能实现

前端功能实现

先安装axios
npm i axios –save

// 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'

Vue.config.productionTip = false

// 引入axios,并配置基础路径
// 又因是跨域请求node端,所以所有请求前页面都要添加node端的基础地址,以后打包上线时再删掉
// 又因是跨域请求,需要配置withCredentials为true,这样避免每次都被识别为新的请求
// 说明:在vue中,可以使用代理去实现跨域,但是每次新地址都需要配置,还是比较麻烦,这里我们采用直接配置跨域,一次配置就可以一劳永逸
import axios from 'axios'
axios.defaults.withCredentials = true // 跨域保存session
axios.defaults.baseURL = "http://localhost:3000" // 默认基础路径配置,打包时删掉
Vue.prototype.$axios = axios

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

在Login.vue中写登录的具体方法

// Login.vue

<template>
  <div class="backlogin">
    <div class="login_box">
      <div class="title">后台登录</div>
      <div>
        <input type="text" placeholder="手机号/用户名" v-model="username" class="myinput" />
      </div>
      <div>
        <input type="password" placeholder="口令" v-model="password" class="myinput" />
      </div>
      <div class="login_other">
        <a href="javascript:;">找回密码</a>
        <input type="checkbox" id="rememberme" /><label for="rememberme">记住我</label>
      </div>
      <button :disabled="disablebtn" @click="login" class="login">{{ loginText }}</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      username: "admin", /* 先预存测试值,以免手动输入 */
      password: "123456",
      disablebtn: false,
      loginText: "登录"
    }
  },
  methods: {
    login () {
      this.disablebtn = true
      this.loginText = "登录中..."
      this.$axios.post('/users/login', {
        username: this.username,
        password: this.password
      }).then((result) => {
        // 成功
        console.log(result);
        this.disablebtn = false
        this.loginText = "登录"
      }).catch((error) => {
        // 失败
        this.disablebtn = false
        this.loginText = "登录"
      })
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .login_box {
    width: 320px;
    margin: 50px auto;
  }
  .login_box .title {
    color: #273444;
    font-size: 1.5em;
    text-align: center;
    margin: 0 0 20px 0;
  }
  .login_box .myinput {
    width: 100%;
    border: 1px solid #cad3d3;
    height: 40px;
    line-height: 40px;
    margin: 5px 0 10px;
    border-radius: 3px;
    padding: 0 10px;
    outline: none;
    box-sizing: border-box;
  }
  .login_box .myinput:focus {
    border: 1px solid #4289dc;
  }
  .login_other {
    overflow: hidden;
  }
  .login_other a {
    float: right;
    color: #727f8f;
  }
  .login_other a:hover {
    color: #273444;
  }
  .login_other input, .login_other label {
    float: left;
    color: #727f8f;
  }
  .login_other input {
    margin: 4px 5px 0 0;
  }
  .login {
    box-sizing: border-box;
    border: 0;
    height: 44px;
    line-height: 44px;
    width: 100%;
    background: #4187db;
    font-size: 16px;
    border-radius: 3px;
    margin-right: 40px;
    transition: all 0.5s ease;
    cursor: pointer;
    outline: none;
    color: #fff;
    margin-top: 15px;
  }
  .login:hover {
    background: #2668b5;
  }
  .login[disabled] {
    opacity: .8;
  }
  .login[disabled]:hover {
    background: #4187db;
  }
  @media only screen and (max-width: 768px) {
    .login_box {
      width: 280px;
      margin: 50px auto;
    }
  }
</style>

后台功能实现

routes中创建dbhandler.js文件,写入下面我们封装好的mongodb操作方法

// dbhandler.js

var mongo=require("mongodb");
var MongoClient = mongo.MongoClient;
var assert = require('assert');
var url = require('url');
var host="localhost";
var port="27017";
var Urls = 'mongodb://localhost:27017/classweb';
// classweb  ===> 自动创建一个


//add一条数据 
var add = function(db,collections,selector,fn){
  var collection = db.collection(collections);
  collection.insertMany([selector],function(err,result){
    try{
        assert.equal(err,null)
        }catch(e){
      console.log(e);
      result = [];
    };

    fn(result);
    db.close();
  });
}
//delete
var deletes = function(db,collections,selector,fn){
  var collection = db.collection(collections);
  collection.deleteOne(selector,function(err,result){
    try{
        assert.equal(err,null);
        assert.notStrictEqual(0,result.result.n);
        }catch(e){
      console.log(e);
      result.result = "";
    };

    fn( result.result ? [result.result] : []); //如果没报错且返回数据不是0,那么表示操作成功。
    db.close;
  });
};
//find
var find = function(db,collections,selector,fn){
  //collections="hashtable";
  var collection = db.collection(collections);

    collection.find(selector).toArray(function(err,result){
      //console.log(docs);
      try{
        assert.equal(err,null);
      }catch(e){
        console.log(e);
        result = [];
      }

      fn(result);
      db.close();
    });

}


//update
var updates = function(db,collections,selector,fn){
  var collection = db.collection(collections);

  collection.updateOne(selector[0],selector[1],function(err,result){
      try{
        assert.equal(err,null);
        assert.notStrictEqual(0,result.result.n);
        }catch(e){
      console.log(e);
      result.result = "";
    };

    fn( result.result ? [result.result] : []); //如果没报错且返回数据不是0,那么表示操作成功。
    db.close();
  });

}
var methodType = {
    // 项目所需
  login:find,
  //   type ---> 不放在服务器上面
  //  放入到服务器
  //  请求---> 根据传入进来的请求 数据库操作
  //  req.query    req.body
  show:find, //后台部分
  add:add,
  update:updates,
  delete:deletes,
  updatePwd:updates,
  //portal部分
  showCourse:find,
  register:add
};
//主逻辑    服务器  , 请求    --》 
// req.route.path ==》 防止前端的请求 直接操作你的数据库
module.exports = function(req,res,collections,selector,fn){
  MongoClient.connect(Urls, function(err, db) {
    assert.equal(null, err);
    console.log("Connected correctly to server");
    // 根据 请求的地址来确定是什么操作  (为了安全,避免前端直接通过请求url操作数据库)
    methodType[req.route.path.substr(1)](db,collections,selector,fn);

    db.close();
  });

};

修改自动生成的 users.js
安装如下模块:
npm i express-session crypto [email protected]
在dbhander.js中配置了login对应的操作是查询,返回数据放到数组中。如果数组空,就表示没查到数据,如果非空,比较密码是否一致,如果都正确,就返回登录成功

// routers/users.js


var express = require('express');
var router = express.Router();
var handler = require('./dbhandler');
var crypto = require('crypto'); // crypto是加密包,对传输过来的密码进行加密

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

// 登录
router.post('/login', (req, res, next) => {
  var md5 = crypto.createHash('md5');
  var password = md5.update(req.body.password).digest('base64');
  handler(req, res, "users", {name: req.body.username}, (data) => {
    console.log(data)
    if (data.length === 0) {
      res.end('{"err": "抱歉,系统中并无该用户,如有需要,请向管理员申请"}');
    } else if (data[0].password !== password) {
      res.end('{"err": "密码不正确"}');
    } else if (data.length !== 0 && data[0].password === password) {
      req.session.username = req.body.username; // 存session
      req.session.password = password;
      res.end('{"success": "true"}');
    }
  })
})


module.exports = router;


这样请求的代码就写完了,但是跨域请求 需要在node中也作配置才可以请求到

修改app.js,在11行左右找到 var app= express(),在其后面添加如下代码

第二段代码是服务器端存session的,直接使用express-session模块,然后添加配置项即可(配置项的说明在备注中)

// app.js

// 跨域(后面上线的时候需要删掉)
app.all('*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', "http://localhost:8088"); // 为了跨域保持session,需指定地址,不能用*
  res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.header('Access-Control-Allow-Credentials', true); 
  next();
})

// session
var session = require('express-session');
app.use(session({
  secret: 'classweb0731', // 设置session签名
  name: 'classweb',
  cookie: {maxAge: 60 * 1000 * 60 * 24}, // 存储时间 24 小时
  resave: false, // 每次请求都重新设置session
  saveUninitialized: true
}))

// server架构
.
|-- app.js
|-- bin
|   `-- www
|-- node_modules // 省略
|-- package-lock.json
|-- package.json
|-- public
|   |-- images
|   |-- javascripts
|   `-- stylesheets
|-- routes
|   |-- dbhandler.js
|   |-- index.js
|   `-- users.js
|-- tree.txt
`-- views
    |-- error.jade
    |-- index.jade
    `-- layout.jade

117 directories, 11 files

// vueclient

.
|-- README.md
|-- build
|   |-- build.js
|   |-- check-versions.js
|   |-- logo.png
|   |-- utils.js
|   |-- vue-loader.conf.js
|   |-- webpack.base.conf.js
|   |-- webpack.dev.conf.js
|   `-- webpack.prod.conf.js
|-- config
|   |-- dev.env.js
|   |-- index.js
|   `-- prod.env.js
|-- index.html
|-- node_modules // 省略。。。
|-- package-lock.json
|-- package.json
|-- src
|   |-- App.vue
|   |-- assets
|   |-- components
|   |-- main.js
|   `-- router
|-- static
`-- tree.txt

759 directories, 18 files

二、后台路由,导航,首页,退出登录

2.1 首页导航 路由配置

上面我们已经实现了登录功能,那么接着我就需要写登录完成后跳转的页面
项目中需要一个字体图标库 fontawesome,
下载地址:http://fontawesome.dashgame.com/
下载好以后把css和font放到static中,然后我们在index.html中引入

.// static结构
|-- css
|   `-- font-awesome.min.css
`-- fonts
    |-- FontAwesome.otf
    |-- fontawesome-webfont.eot
    |-- fontawesome-webfont.svg
    |-- fontawesome-webfont.ttf
    |-- fontawesome-webfont.woff
    `-- fontawesome-webfont.woff2

// 根目录下index.html

<link rel="stylesheet" type="text/css" href="../static/css/font-awesome.min.css" />

注: 为什么是 ../static 这样去找static,而不是 ./ ,因为当进入二级路由以后,在路由内部index和static就不再被认为是同一级,就找不到了,所以就通过 ../往上再找了一级

我们要设置一些统一的全局样式,我们就直接写在 index.html中,这里本来不是一次就写完这些样式,但为了避免以后再回来添加样式,这里就一起写了,首先清楚了全局的margin等,然后定义了 .btn按钮样式 .myinput输入框样式,以后再使用

// index.html

<style>
  *{
      margin: 0;
      padding: 0;
  }
  body{
      font-size: 14px;
      font-family: arial "microsoft yahei";
      background: #f0f2f5;
  }
  ul,li{
      list-style: none;
  }
  /*按钮*/
  .btn{
      border:1px solid #4187db;
      color: #4187db;
      background: #fff;
      padding: 6px 14px 7px;
      border-radius: 3px;
      transition: all 0.5s ease;
      outline: none;
      margin-top: 14px;
      cursor: pointer;
  }
  .btn i{
      margin-right: 4px;
  }
  .btn:hover{
      background: #4187db;
      color: #fff;
  }

  /*输入框*/
  .myinput{
      width: 65%;
      border: 1px solid #cad3de;
      height: 35px;
      line-height: 35px;
      margin: 5px 0 10px;
      border-radius: 3px;
      padding: 0 10px;
      outline: none;
      box-sizing: border-box;
  }
  .myinput:focus{
      border: 1px solid #4289dc;
  }
</style>

assets文件夹中创建 images文件夹,放入我们backIndex.vue中需要的图片

修改路由文件 index.js,并且在components中创建 backIndex.vue组件

// router/index.js

import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'
import BackIndex from '@/components/BackIndex'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login
    },
    {
      path: '/backIndex', // 首页框架
      name: 'BackIndex',
      component: BackIndex
    }
  ]
})

// BackIndex.vue

<template>
  <div></div>
</template>

在BackIndex.vue组件中写入后面代码
基本功能如下图,左侧导航,顶部搜索栏和个人头像 退出等操作

// BackIndex.vue页面骨架

<template>
  <div class="backlogin">
    <!-- 顶部 -->
    <div class="header">
      <div class="search_box">
        <i class="fa fa-search" aria-hidden="true"></i>
        <input type="text">
      </div>
      <div class="handler">
        <div class="more">
          <i class="fa fa-bars" aria-hidden="true"></i>
          <ul>
            <li><a href="javascript:;"><i class="fa fa-sign-out" aria-hidden="true"></i></a></li>
            <li><a href="javascript:;">修改密码</a></li>
            <li><a href="javascript:;">意见反馈</a></li>
          </ul>
        </div>
        <img src="../assets/images/teacherimg01.png" alt="" />
      </div>
    </div>

    <!-- 侧面导航 -->
    <div class="sidenav_box">
      <img src="../assets/images/logo03.png" alt="" class="logo" />
      <ul class="sidenav">
        <li>
          <router-link to="/backIndex/indexContent">
            <i class="fa fa-home" arial-hidden="true"></i>
            <span>网站首页</span>
          </router-link>
        </li>
        <li>
          <router-link to="/backIndex/adminList">
            <i class="fa fa-user-o" arial-hidden="true"></i>
            <span>后台人员</span>
          </router-link>
        </li>
        <li>
          <router-link to="/backIndex/studentList">
            <i class="fa fa-user-circle-o" arial-hidden="true"></i>
            <span>学员管理</span>
          </router-link>
        </li>
        <li>
          <router-link to="/backIndex/courseList">
            <i class="fa fa-book" arial-hidden="true"></i>
            <span>课程管理</span>
          </router-link>
        </li>
      </ul>
    </div>

    <!-- 内容区 -->
    <div class="content">
      <ul class="breadcrumb">
        <li><a href="#/backIndex">首页</a></li>
        <li>网站首页</li>
      </ul>
      <!-- <router-view></router-view> -->
    </div>
  </div>
</template>
// BackIndex.vue

<template>
  <div class="backlogin">
    <!-- 顶部 -->
    <div class="header">
      <div class="search_box" :class="{ search_box_fouce: search_box_fouce }">
        <i class="fa fa-search" aria-hidden="true"></i>
        <input type="text" placeholder="搜索..." @focus="focusFn" @blur="blurFn" />
      </div>
      <div class="handler">
        <div class="more" @click="toggleSlide">
          <i class="fa fa-bars" aria-hidden="true"></i>
          <ul :class="{ showul: showExit }">
            <li><a href="javascript:;" @click="logout"><i class="fa fa-sign-out" aria-hidden="true"></i>退出</a></li>
            <li><a href="javascript:;">修改密码</a></li>
            <li><a href="javascript:;">意见反馈</a></li>
          </ul>
        </div>
        <img src="../assets/images/teacherimg01.png" alt="" />
      </div>
    </div>

    <!-- 侧面导航 -->
    <div class="sidenav_box">
      <img src="../assets/images/logo03.png" alt="" class="logo" />
      <ul class="sidenav">
        <li>
          <router-link to="/backIndex/indexContent">
            <i class="fa fa-home" arial-hidden="true"></i>
            <span>网站首页</span>
          </router-link>
        </li>
        <li>
          <router-link to="/backIndex/adminList">
            <i class="fa fa-user-o" arial-hidden="true"></i>
            <span>后台人员</span>
          </router-link>
        </li>
        <li>
          <router-link to="/backIndex/studentList">
            <i class="fa fa-user-circle-o" arial-hidden="true"></i>
            <span>学员管理</span>
          </router-link>
        </li>
        <li>
          <router-link to="/backIndex/courseList">
            <i class="fa fa-book" arial-hidden="true"></i>
            <span>课程管理</span>
          </router-link>
        </li>
      </ul>
    </div>

    <!-- 内容区 -->
    <div class="content">
      <ul class="breadcrumb">
        <li><a href="#/backIndex">首页</a></li>
        <li>{{ pageTitle }}</li>
      </ul>
      <router-view></router-view>
    </div>
  </div>
</template>
<script>
  var pageTitleObj = {
    indexContent: '网站首页',
    adminList: '后台人员',
    studentList: '学员管理',
    courseList: '课程管理',
    courseEdit: '课程编辑'
  }
  export default {
    name: "backlogin",
    data () {
      return {
        search_box_fouce: false,
        showExit: false,
        pageTitle: pageTitleObj[this.$route.path.substr(this.$route.path.lastIndexOf('/') + 1)] || "网站首页"
      }
    },
    methods: {
      // 搜索框获取焦点,添加class
      focusFn () {
        this.search_box_fouce = true
      },
      // 搜索框失去焦点,去掉class
      blurFn () {
        this.search_box_fouce = false
      },
      // 头像旁边下拉框的显示与隐藏
      toggleSlide () {
        this.showExit = !this.showExit
      },
      // 退出系统
      logout () {

      }
    },
    watch: {
      $route: {
        handler (val, oldVal) {
          var path = val.path;
          this.pageTitle = pageTitleObj[path.substr(path.lastIndexOf("/") + 1)] || "网站首页"
        }
      }
    }
  }
</script>
<style scoped>
  ul, li {
    list-style: none;
  }
/* 顶部栏 */
  .header {
    height: 60px;
    box-shadow: 0 1px 5px rgba(13, 62, 73, .2);
    background: #fff;
    margin-left: 80px;
    min-width: 740px;
  }
  .search_box {
    color: #979fa8;
    padding-top: 20px;
    float: left;
  }
  .search_box i {
    margin: 0 12px 0 70px;
    transition: all 0.5s ease;
  }
  .search_box input {
    border: none;
    outline: none;
  }

  .search_box_fouce i {
    margin-left: 55px;
    color: #2c3d50;
  }
  .handler > * {
    float: right;
    margin-right: 20px;
    cursor: pointer;
  }
  .handler .more {
    font-size: 20px;
    color: #566a80;
    margin: 15px 30px 0 0;
    position: relative;
  }
  .handler .more:hover {
    color: #2c3d50;
  }
  .handler .more ul {
    font-size: 14px;
    position: absolute;
    right: 0;
    top: 55px;
    width: 120px;
    box-shadow: 0 1px 5px rgba(13, 62, 73, .2);
    transition: all 0.3s ease-out;
    height: 0;
    opacity: 0;
    overflow: hidden;
    text-align: center;
  }
  .handler .more .showul {
    height: auto;
    top: 45px;
    opacity: 1;
    border-top: 1px solid #979fa8;
  }
  .handler .more a {
    display: block;
    padding: 8px 10px;
    background: #fff;
    color: #566a80;
    text-decoration: none;
  }
  .handler .more a:hover {
    background: #f8f9fb;
  }
  .handler > img {
    width: 50px;
    border-radius: 50%;
    margin-top: 5px;
    margin-right: 30px;
  }
/* 侧边栏 */
  .sidenav_box {
    width: 80px;
    box-shadow: 0 1px 5px rgba(13, 62, 73, .2);
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    background: #fff;
    z-index: 99;
  }
  .sidenav_box .logo {
    width: 46px;
    margin: 20px 0 0 17px;
  }
  .sidenav {
    margin-top: 30px;
  }
  .sidenav li {
    margin-bottom: 20px;
  }
  .sidenav a {
    display: block;
    width: 56px;
    height: 56px;
    margin: 0 auto;
    position: relative;
    cursor: pointer;
    opacity: 0.6;
    transition: all 0.5s ease;
    text-decoration: none;
  }
  .sidenav a:hover {
    background: #f0f2f5;
    opacity: 1;
  }
  .sidenav a i {
    display: block;
    font-size: 20px;
    line-height: 56px;
    text-align: center;
    color: #566a80;
  }
  .sidenav a span {
    position: absolute;
    left: 55px;
    top: 22px;
    background: #000;
    color: #fff;
    width: 0;
    padding: 5px 0;
    border-radius: 3px;
    font-size: 12px;
    opacity: 0;
  }
  .sidenav a span:after {
    content: "";
    position: absolute;
    top: 8px;
    left: -10px;
    border: 5px solid transparent;
    border-right-color: #000;
  }
  .sidenav a:hover span {
    opacity: 1;
    left: 65px;
    width: 60px;
    padding: 5px 20px;
    transition: none 0.5s ease-out;
    transition-property: opacity, left;
  }
  .sidenav .router-link-active {
    opacity: 1;
    background: #f0f2f5;
  }
  .sidenav .router-link-active:after {
    content: "";
    position: absolute;
    left: -16px;
    top: 8px;
    height: 40px;
    width: 8px;
    border-radius: 3px;
    background: #566a80;
  }
/* 主页内容 */
  .content {
    margin: 20px 30px 0px 100px;
    min-height: 300px;
    min-width: 700px;
  }
  .breadcrumb {
    border-radius: 4px;
    padding: 10px 15px;
    background: #fff;
  }
  .breadcrumb > li {
    display: inline-block;
    color: #777;
  }
  .breadcrumb > li + li:before {
    padding: 0 5px;
    color: #ccc;
    content: "/\00a0";
  }
  .breadcrumb > li > a {
    color: #32475f;
    text-decoration: none;
  }


</style>

在地址栏输入 http://localhost:8088/#/backIndex 就可以看到首页框架的效果了。 (这时候内部页面还没有,所以点击左侧导航会找不到页面,先不要点)

下面继续将所有的路由配置其他页面的路由

// indext.js

import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'
import BackIndex from '@/components/BackIndex' // 首页框架
import CourseList from '@/components/CourseList' // 课程列表
import IndexContent from '@/components/IndexContent' // 首页统计
import AdminList from '@/components/AdminList' // 后台用户
import StudentList from '@/components/StudentList' // 学员用户
import CourseEdit from '@/components/CourseEdit' // 编辑课程

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login
    },
    {
      path: '/backIndex', // 首页框架
      name: 'BackIndex',
      component: BackIndex,
      children: [
        { path: 'courseList', component: CourseList }, // 课程列表
        { path: 'indexContent', component: IndexContent }, // 首页统计
        { path: 'adminList', component: AdminList }, // 后台用户
        { path: 'studentList', component: StudentList }, // 学员用户
        { path: 'courseEdit', component: CourseEdit }, // 编辑课程
        { path: '*', redirect: 'indexContent' }
      ]
    }
  ]
})
添加如下相应页面视图模板
CourseList from  // 课程列表
IndexContent from  // 首页统计
AdminList from  // 后台用户
StudentList  // 学员用户
CourseEdit  // 编辑课程

// 以上视图模板内容如下:
<template>
  <div>

  </div>
</template>

再刷新页面的时候,左侧导航就可以点击了

2.2 首页统计页面

下面为 indexContent.vue 添加中间显示的统计图表,代码在后面

canvas图表详解系列(2):折线图
http://www.cnblogs.com/chengduxiaoc/p/7678967.html

// indexContent.vue

<template>
  <div class="indexContent main">
    <h4>最新数据</h4>
    <ul class="number">
            <li>
                <div class="title">今日访问</div>
                <p>12000</p>
                <a href="javascript:;">查看详情<i class="fa fa-angle-right" aria-hidden="true"></i></a>
            </li>
            <li>
                <div class="title">学员总数</div>
                <p>3000000</p>
                <a href="javascript:;">查看详情<i class="fa fa-angle-right" aria-hidden="true"></i></a>
            </li>
            <li>
                <div class="title">在学人数</div>
                <p>2000</p>
                <a href="javascript:;">查看详情<i class="fa fa-angle-right" aria-hidden="true"></i></a>
            </li>
        </ul>
    <canvas id="barChart" height="400" width="600" style="margin:10px 0"> 你的浏览器不支持HTML5 canvas </canvas>

  </div>
</template>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .main{
        border-radius: 4px;
        background: #fff;
        margin-top: 10px;
        overflow: hidden;
    }
    .main > h4{
        color: #51555a;
    padding:10px;
    border-bottom: 1px solid #DFE3EA;
    }
    .number{
        width: 30%;
        float: right;
        margin-right: 10%;
        margin-top: 10px;
        color: #566A80;
    }
    .number li{
        padding: 20px;
        border-top:1px solid #F0F2F5;
    }
    .number li:first-child{
        border: none 0;
    }
    .number p{
        font-size: 20px;
        font-family: arial;
        margin: 10px 0;
    }
    .number a{
        text-decoration: none;
        color: #4187db;
        font-size: 12px;
    }
    .number li:hover{
        color: #173859;
    }
    .number a:hover{

    }
    .number i{
        transition: all 0.3s ease-out;
        padding-left: 10px;
    }
    .number a:hover i{
        padding-left: 20px;
    }
    .number:hover li{
        border-color:#DFE3EA
    }
    canvas{
        max-width: 55%;
        min-width: 45%;
    }
</style>

<script>

    export default {
      name: 'indexContent',
      data () {
        return {

        }
      },
      methods:{
        },
        mounted:function(){
            var chartData = [["2017/01", 50], ["2017/02", 60], ["2017/03", 100], ["2017/04",200], ["2017/05",350], ["2017/06",600]];
            goBarChart(chartData);

        }
    }

    function goBarChart(dataArr){


    // 声明所需变量
    var canvas,ctx;
    // 图表属性
    var cWidth, cHeight, cMargin, cSpace;
    var originX, originY;
    // 折线图属性
    var tobalDots, dotSpace, maxValue;
    var totalYNomber;
    // 运动相关变量
    var ctr, numctr, speed;

    // 获得canvas上下文
    canvas = document.getElementById("barChart");
    if(canvas && canvas.getContext){
        ctx = canvas.getContext("2d");
    }
    initChart(); // 图表初始化
    drawLineLabelMarkers(); // 绘制图表轴、标签和标记
    drawBarAnimate(); // 绘制折线图的动画

    //点击刷新图表
    canvas.onclick = function(){
        initChart(); // 图表初始化
        drawLineLabelMarkers(); // 绘制图表轴、标签和标记
        drawBarAnimate(); // 绘制折线图的动画
    };

    // 图表初始化
    function initChart(){
        // 图表信息
        cMargin = 60;
        cSpace = 80;
        canvas.width = Math.floor( (window.innerWidth-100)/2 ) * 2 ;
        canvas.height = 740;
        canvas.style.height = canvas.height/2 + "px";
        canvas.style.width = canvas.width/2 + "px";
        cHeight = canvas.height - cMargin - cSpace;
        cWidth = canvas.width - cMargin - cSpace;
        originX = cMargin + cSpace;
        originY = cMargin + cHeight;

        // 折线图信息
        tobalDots = dataArr.length;
        dotSpace = parseInt( cWidth/tobalDots );
        maxValue = 0;
        for(var i=0; i<dataArr.length; i++){
            var dotVal = parseInt( dataArr[i][1] );
            if( dotVal > maxValue ){
                maxValue = dotVal;
            }
        }
        maxValue += 50;
        totalYNomber = 10;
        // 运动相关
        ctr = 1;
        numctr = 100;
        speed = 6;

        ctx.translate(0.5,0.5);  // 当只绘制1像素的线的时候,坐标点需要偏移,这样才能画出1像素实线
    }

    // 绘制图表轴、标签和标记
    function drawLineLabelMarkers(){
        ctx.font = "24px Arial";
        ctx.lineWidth = 2;
        ctx.fillStyle = "#566a80";
        ctx.strokeStyle = "#566a80";
        // y轴
        drawLine(originX, originY, originX, cMargin);
        // x轴
        drawLine(originX, originY, originX+cWidth, originY);

        // 绘制标记
        drawMarkers();
    }

    // 画线的方法
    function drawLine(x, y, X, Y){
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(X, Y);
        ctx.stroke();
        ctx.closePath();
    }

    // 绘制标记
    function drawMarkers(){
        ctx.strokeStyle = "#E0E0E0";
        // 绘制 y 轴 及中间横线
        var oneVal = parseInt(maxValue/totalYNomber);
        ctx.textAlign = "right";
        for(var i=0; i<=totalYNomber; i++){
            var markerVal =  i*oneVal;
            var xMarker = originX-5;
            var yMarker = parseInt( cHeight*(1-markerVal/maxValue) ) + cMargin;
            //console.log(xMarker, yMarker+3,markerVal/maxValue,originY);
            ctx.fillText(markerVal, xMarker, yMarker+3, cSpace); // 文字
            if(i>0){
                drawLine(originX+2, yMarker, originX+cWidth, yMarker);
            }
        }
        // 绘制 x 轴 及中间竖线
        ctx.textAlign = "center";
        for(var i=0; i<tobalDots; i++){
            var markerVal = dataArr[i][0];
            var xMarker = originX+i*dotSpace;
            var yMarker = originY+30;
            ctx.fillText(markerVal, xMarker, yMarker, cSpace); // 文字
            if(i>0){
                drawLine(xMarker, originY-2, xMarker, cMargin    );
            }
        }
        // 绘制标题 y
        ctx.save();
        ctx.rotate(-Math.PI/2);
        ctx.fillText("访问量", -canvas.height/2, cSpace-10);
        ctx.restore();
        // 绘制标题 x
        ctx.fillText("月份", originX+cWidth/2, originY+cSpace/2+20);
    };

    //绘制折线图
    function drawBarAnimate(){
        ctx.strokeStyle = "#566a80";  //"#49FE79";

        //连线
        ctx.beginPath();
        for(var i=0; i<tobalDots; i++){
            var dotVal = dataArr[i][1];
            var barH = parseInt( cHeight*dotVal/maxValue* ctr/numctr );//
            var y = originY - barH;
            var x = originX + dotSpace*i;
            if(i==0){
                ctx.moveTo( x, y );
            }else{
                ctx.lineTo( x, y );
            }
        }
        ctx.stroke();

        //背景
        ctx.lineTo( originX+dotSpace*(tobalDots-1), originY);
        ctx.lineTo( originX, originY);
        //背景渐变色
        //柱状图渐变色
        var gradient = ctx.createLinearGradient(0, 0, 0, 300);
        gradient.addColorStop(0, 'rgba(133,171,212,0.6)');
        gradient.addColorStop(1, 'rgba(133,171,212,0.1)');
        ctx.fillStyle = gradient;
        ctx.fill();
        ctx.closePath();
        ctx.fillStyle = "#566a80";

        //绘制点
        for(var i=0; i<tobalDots; i++){
            var dotVal = dataArr[i][1];
            var barH = parseInt( cHeight*dotVal/maxValue * ctr/numctr );
            var y = originY - barH;
            var x = originX + dotSpace*i;
            drawArc( x, y );  //绘制点
            ctx.fillText(parseInt(dotVal*ctr/numctr), x+15, y-8); // 文字
        }

        if(ctr<numctr){
            ctr++;
            setTimeout(function(){
                ctx.clearRect(0,0,canvas.width, canvas.height);
                drawLineLabelMarkers();
                drawBarAnimate();
            }, speed);
        }
    }

    //绘制圆点
    function drawArc( x, y, X, Y ){
        ctx.beginPath();
        ctx.arc( x, y, 3, 0, Math.PI*2 );
        ctx.fill();
        ctx.closePath();
    }


    }

</script>

2.3 登录功能完善

登录请求完成以后,如果出错,就弹出错误(我们本项目没有封装模态框,就直接用alert吧),
如果正确,就跳转到首页

修改后的 login.vue中的 ajax请求代码如下:

    login () {
      this.disablebtn = true
      this.loginText = "登录中..."
      this.$axios.post('/users/login', {
        username: this.username,
        password: this.password
      }).then((result) => {
        // 成功
        // console.log(result);
        if (result.data.err) {
          alert(result.data.err);
        } else {
          this.$router.push({path: '/backIndex/indexContent'})
        }
        this.disablebtn = false
        this.loginText = "登录"
      }).catch((error) => {
        // 失败
        this.disablebtn = false
        this.loginText = "登录"
      })
    }

注:我们通过 router.push去修改url,作用和原生js的 window.location.href基本一致

2.4 退出系统

BackIndex.vue中我们有一个退出登录的空方法,我们在里面写退出登录的请求的代码,退出成功后跳转到根目录

      // 退出系统
      logout () {
        this.$axios.post('/users/logout', {

        }).then((result) => {
          this.$router.push({path: '/'});
        }).catch((error) => {
          console.log(error);
        })
      }

然后在后台写接口,在user.js中 登录的方法后面写(修改完成后需要重启node服务)

注:这里直接清除登录中设置的 session 就可以了,(我们后面会对所有的请求设置拦截,如果session中的用户信息没有,再提示用户未登录,跳转到登录页面就可以了)

// 退出
router.post('/logout', (req, res, next) => {
  req.session.username = ""; // 清除session
  req.session.password = "";
  res.end('{"success": "true"}');
})

到此,我们就实现了登录,显示首页,退出的基本功能

三、用户添加/修改/删除 vue表格组件 vue分页组件

3.1 用户添加/修改/删除 表格组件 分页组件

由于要用到表格,我们这里就得封装 表格和分页组件
先在componets中创建分页组件 pagebar.vue,写入以下代码(功能是传入分页信息,然后展示分页,点击分页的时候,会向上触发goto()跳转到第几页

// pageBar.vue

<template>
  <div>
    <ul class="pagination">
      <li :class="{hideLi: current == 1}" @click="goto(current - 1)">
        <a href="javascript:;" arial-label="Previous">
          <span arial-hidden="true">&laquo;</span>
        </a>
      </li>
      <li v-for="(index, key) in pages" @click="goto(index)" :key="index" :class="{'active': current == index}">
        <a href="javascript:;">{{ index }}</a>
      </li>
      <li :class="{hideLi: (allpage == current || allpage == 0)}" @click="goto(current + 1)">
        <a href="javascript:;" arial-label="Next">
          <span arial-hidden="true">&raquo;</span>
        </a>
      </li>
    </ul>
  </div>
</template>
<script>
  /**
   * 分页组件
   * 设置props
   *    current 当前页 默认1
   *    showItem 显示几页 默认5
   *    allpage 总页数 10
   */

  export default {
    name: 'page',
    data () {
      return {

      }
    },
    props: {
      current: {
        type: Number,
        default: 1
      },
      showItem: {
        type: Number,
        default: 5
      },
      allpage: {
        type: Number,
        default: 10
      }
    },
    computed: {
      pages () {
        var pag = [];
        if (this.current < this.showItem) {
          var i = Math.min(this.showItem, this.allpage);
          while (i) {
            pag.unshift(i--);
          }
        } else {
          var middle = this.current - Math.floor(this.showItem / 2),
              i = this.showItem;
          if (middle > (this.allpage - this.showItem)) {
            middle = (this.allpage - this.showItem) + 1
          }
          while (i--) {
            pag.push(middle++);
          }
        }
        return pag;
      }
    },
    methods: {
      goto (index) {
        if (index == this.current) return;
        this.$emit('on-gopage', index);
      }
    }
  }
</script>
<style scoped>
  .pagination {
    margin: 10px;
    display: inline-block;
  }
  .pagination li {
    display: inline;
  }
  .pagination li a, 
  .pagination li span {
    float: left;
    padding: 6px 12px;
    margin-left: -1px;
    line-height: 1.42857143;
    color: #4187db;
    text-decoration: none;
    background: #fff;
    border: 1px solid #f8f9fb;
  }
  .pagination li a:hover {
    background-color: #f8f9fb;
  }
  .pagination .active a {
    background-color: #4187db !important;
    color: #fff;
  }
  .hideLi a {
    visibility: hidden;
  }
</style>
// Grid.vue

<template>
  <div>
    <table cellspacing="" cellpadding="" border="">
      <thead>
        <tr>
          <th>序号</th>
          <th v-for="(item, index) in theadData">{{ item.title }}</th>
        </tr>
      </thead>
      <tbody>
        <tr v-if="!listData.length">
          <td>1</td>
          <td>没有数据... ...</td>
          <td v-for="(item, index) in theadData" v-if="index <= theadData.length - 2"></td>
        </tr>
        <tr v-for="(item, index) in listData">
          <td>{{ index + 1 }}</td>
          <td v-for="(item2, index2) in theadData">
            <span v-if="index2 === 0" style="float: right;">
              <i title="编辑" v-if="ifEdit" class="fa fa-edit" aria-hidden="true" @click="editHandler(item)"></i>
              <i title="删除" v-if="ifDelete" class="fa fa-trash" aria-hidden="true" @click="deleteHandler(item)"></i>
              <i title="下移" v-if="ifDown" class="fa fa-arrow-circle-o-down" aria-hidden="true" @click="downHandler(item)"></i>
              <i title="上移" v-if="ifUp" class="fa fa-arrow-circle-o-up" aria-hidden="true" @click="upHandler(item)"></i>
              <i title="封号" v-if="ifReset" class="fa fa-unlock-alt" aria-hidden="true" @click="resetHandler(item)"></i>
            </span>
            {{ item[item2.keyname] }}
          </td>
        </tr>
      </tbody>
    </table>

    <pagebar 
      v-if="ifpage" 
      :current="pageInfo.current" 
      :showItem="pageInfo.showItem" 
      :allpage="pageInfo.allpage"
      @on-gopage="gopage"></pagebar>
  </div>
</template>
<script>
/**
 * 表格组件
 * 设置props
 *    theadData 表头数据 默认[]
 *    listData 表格数据 默认[]
 *    ifpage  是否分页 默认true
 *    isEdit/ifDelete/ifUp/ifDown 是否可编辑/删除/上下移动 默认false
 * 
 * 定制模板
 *    slot为grid-thead 定制表格头部
 *    slot为grid-handler 定制表格操作
 * 
 * 监听状态变化
 *    on-delete 删除
 *    on-edit   编辑
 *    on-up     上移
 *    on-down   下移
 * 
 * 分页
 * pageInfo 分页信息如下 默认{} -- 或单独使用pagebar.vue
 * {
 *    current: 当前第几页 默认1
 *    showItem: 显示多少页 默认5
 *    allpage:总页数   默认10
 * }
 */  
import pagebar from './pagebar'  
export default {
  name: 'grid',
  data () {
    return {

    }
  },
  props: {
    listData: {
      type: Array,
      // default: function () {
      default () {
        return [{
          name: "没有数据..."
        }]
      }
    },
    theadData: {
      type: Array,
      // default: function () {
      default () {
        return [{
          title: "名字",
          keyname: "name"
        }]
      }
    },
    ifpage: {
      type: Boolean,
      default: true
    },
    ifEdit: {
      type: Boolean,
      default: false
    },
    ifDelete: {
      type: Boolean,
      default: false
    },
    ifUp: {
      type: Boolean,
      default: false
    },
    ifDown: {
      type: Boolean,
      default: false
    },
    ifReset: {
      type: Boolean,
      default: false
    },
    pageInfo: {
      type: Object,
      default: function () {
        return {}
      }
    }
  },
  methods: {
    editHandler (item) {
      this.$emit('on-edit', item)
    },
    deleteHandler (item) {
      this.$emit('on-delete', item)
    },
    downHandler (item) {
      this.$emit('on-down', item)
    },
    upHandler (item) {
      this.$emit('on-up', item)
    },
    resetHandler (item) {
      this.$emit('on-reset', item)
    },
    gopage (index) {
      this.$emit('on-gopage', index)
    }
  },
  components: {
    pagebar
  }
}
</script>
<style scoped>
  table {
    border: none 0;
    border-collapse: collapse;
    color: #51555a;
    width: 100%;
    border-bottom: 1px solid #dfe3ea;
  }
  td, th {
    padding: 10px 20px;
    text-align: left;
    border-width: 0;
  }
  thead tr, tr:nth-of-type(even) {
    background: #f8f9fb;
  }
  td .fa {
    padding: 0 5px;
    cursor: pointer;
    opacity: 0;
    transition: all 0.3s ease;
  }
  td .fa:first-child {
    margin-left: 10px;
  }
  tr:hover .fa {
    opacity: 1;
  }
  td .fa:hover {
    color: #4187db;
    transform: scale(1.2);
  }
</style>

// AdminList.vue

<template>
<div class="adminList main">
  <div class="input_box">
    <input class="myinput" type="text" placeholder="用户名" v-model="Admin.name" />
    <input class="myinput" type="text" placeholder="手机号" v-model="Admin.phone" />
    <input class="myinput" type="password" placeholder="密码" v-if="!editAdminOjb" v-model="Admin.password" />
    <button class="btn" v-if="!editAdminOjb" @click="addAdmin()"><i class="fa fa-plus" aria-hidden="true"></i>添加</button>
    <button class="btn" v-if="!editAdminOjb" @click="saveEditAdmin()"><i class="fa fa-save" aria-hidden="true"></i>保存</button>
    <button class="btn" v-if="!editAdminOjb" @click="cancelEditAdmin()" style="opacity: 0.8;"><i class="fa fa-times-circle-o" aria-hidden="true"></i>取消</button>
  </div>
  <grid 
    :listData="listData"
    :theadData="theadData"
    :ifEdit="true"
    :ifDelete="true"
    :ifpage="true"
    :pageInfo="pageInfo"
    @on-delete="deleteAdmin"
    @on-edit="editAdmin"
    @on-gopage="gopage"></grid>
</div>
</template>
<script>
  var theadData = [
    { title: '用户名', keyname: 'name'},
    { title: '手机号', keyname: 'phone'}
  ];
  import grid from './grid'
  export default {
    name: 'adminList',
    components: { grid },
    data () {
      return {
        listData: [],
        theadData: theadData,
        Admin: { // 用户信息
          name: "",
          phone: "",
          password: ""
        },
        editAdminOjb: null, // 用于存放正在编辑的用户
        pageInfo: {}
      }
    },
    mounted () {
      this.getAdminList(1);
    },
    methods: {
      getAdminList (page) {
        this.$axios.post('/users/AdminList', {
            page:page
          }).then((result) => {
          // 成功
          this.listData = result.data.data;
          this.pageInfo.allpage = Math.ceil(result.data.total / 5);
        }).catch((error) => {
          // 失败
          console.log(error);
        })
      },
      addAdmin () { // 添加用户
        if (!this.Admin.name || !this.Admin.phone || !this.Admin.password) {
          alert('不能为空');
          return false;
        }
        this.$axios.post('/users/add', this.Admin).then((result) => {
          // 成功
          this.getAdminList();
          this.emptyAdmin();
        }).catch((error) => {
          // 失败
          console.log(error);
        })
      },
      editAdmin (item) { // 编辑用户
        this.editAdminOjb = item;
        this.Admin = JSON.parse(JSON.stringify(item));
      },
      saveEditAdmin () {
        if (!this.Admin.name || !this.Admin.phone) {
          alert('不能为空');
          return false;
        }
        this.$axios.post('/users/update', this.Admin).then((result) => {
          // 成功
          this.gopage(this.pageInfo.current);
          this.editAdminOjb = null;
          this.emptyAdmin();
        }).catch((error) => {
          // 失败
          console.log(error);
        })
      },
      cancelEditAdmin () {
        this.editAdminOjb = null;
        this.editAdmin();
      },
      emptyAdmin () { // 清空输入框
        this.Admin.name = "";
        this.Admin.phone = "";
        this.Admin.password = "";
      },
      deleteAdmin (item) {
        this.$axios.post('/users/delete', item).then((result) => {
          // 成功
          this.gopage(this.pageInfo.current);
          this.emptyAdmin();
        }).catch((error) => {
          // 失败
          console.log(error);
        })
      },
      gopage (index) {
        this.pageInfo.current = index;
        // 查询数据
        this.getAdminList(index);
      }
    }
  }
</script>
<style scoped>
  .main {
    border-radius: 4px;
    background: #fff;
    margin-top: 10px;
  }
  .input_box {
    padding: 0 10px;
  }
  .input_box .myinput {
    width: 25%;
  }
</style>



添加增删改用户的接口

于需要对 _id进行转化,我们还需要引入mongodb的ObjectId模块

// routes/users.js
var ObjectId = require('mongodb').ObjectId;



// 管理员列表
router.post('/AdminList', (req, res, next) => {
  req.route.path = '/page'; // 修改page来设定对数据库的操作
  var page = req.body.page || 1;
  var rows = req.body.rows || 5;
  handler(req, res, "users", [{}, {limit: rows, skip: (page - 1) * rows}], (data, count) => {
    var obj = {
      data: data,
      total: count,
      success: '成功'
    };
    var str = JSON.stringify(obj);
    res.end(str);
  })

})

// 添加管理员
router.post('/add', (req, res, next) => {
  var md5 = crypto.createHash('md5');
  req.body.password = md5.update(req.body.password).digest('base64');
  handler(req, res, "users", req.body, (data) => {
    if (data.length == 0) {
      res.end('{"err": "抱歉,添加失败"}');
    } else {
      res.end('{"success": "添加成功"}');
    }
  })
})

// 删除用户
router.post('/delete', (req, res, next) => {
  handler(req, res, "users", {"_id": ObjectId(req.body._id)}, (data) => {
    // console.log(data);
    if (data.length == 0) {
      res.end('{"err": "抱歉,删除失败"}');
    } else {
      var obj = {
        success: '删除成功'
      };
      var str = JSON.stringify(obj);
      res.end(str);
    }
  })
})

// 编辑更新用户
router.post('/update', (req, res, next) => {
  var selectors = [
    {"_id": ObjectId(req.body._id)},
    {"$set": {
      name: req.body.name,
      phone: req.body.phone 
    }}
  ];
  handler(req, res, "users", selectors, (data) => {
    if (data.length == 0) {
      res.end('{"err": "抱歉,修改失败"}');
    } else {
      res.end('{"success": "修改成功"}');
    }
  })
})

响应拦截

不登陆也能请求列表数据,需要对所有的请求进行拦截,只有当登录了,才能请求数据

// app.js   session后面添加内容

// 验证用户登录
app.use((req, res, next) => {
  // 后台请求
  if (req.session.username) { // 表示已登录后台
    next();
  } else if (req.url.indexOf("login") >= 0 || req.url.indexOf("logout") >= 0) {
    next(); // 登入、登出,不需要登录
  } else {
    res.end('{"redirect": "true"}')
  }
})

然后在vue的main.js中 作redirect跳转,还有当后台返回err的处理
这里在axios中作响应前拦截,就是所有的响应到达$req.post的then(){}之前执行的代码,具体的axios配置项大家可以查查axios官网

// main.js

// 添加响应拦截器
axios.interceptors.response.use((response) => {
  // 处理响应数据
  if (response.data.err) {
    alert(response.data.err);
    return Promise.reject(response);
  } else if (response.data.redirect) {
    alert('请先登录...');
    window.location.href = '#/'; // 跳转到登录页
    return Promise.reject(response);
  } else {
    return response; // 返回response后继续执行后面的操作
  }
}, (error) => {
  // 对错误处理做一些响应
  return Promise.reject(error);
})

猜你喜欢

转载自blog.csdn.net/qq_25479327/article/details/81302296