精华内容
下载资源
问答
  • VUE实战项目之喵喵电影

    千人学习 2019-05-09 17:20:30
    2019全新打造Web前端教程,Vue实战项目之喵喵电影,详细讲解项目演示与开发流程。
  • vue实战项目之在线外卖订餐平台
  • Node+Vue实战项目

    万次阅读 多人点赞 2018-07-31 23:42:09
    一、Node+Vue实战项目 1.1 创建Node项目、Vue项目 mkdir classweb cd classweb/ express server vue init webpack vueclient . |-- server | |-- app.js | |-- bin | |-- node_modules | |-- package-lock....

    一、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 mongodb@2.2.33
    在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);
    })
    展开全文
  • vue 实战项目demo

    2018-11-16 16:03:58
    vue移动端实战app分享,使用最新版本vue2.6+vuerouter
  • Vue实战项目开发--Vue中的动画特效
    展开全文
  • VUE实战项目开发_初级

    2020-06-11 19:04:17
    本项目是集vue2.6+vuex+vue- router+Element Ul+ES6+webpack+axios+... 本项目以实战项目为切入点,模拟公司Vue项目研发流程,从头到尾讲解项目功能实现和优化、打包上线流程等,帮助提高你的技术层级,突破技术瓶颈。
  • VUE实战项目开发_高级

    2020-06-11 19:10:45
    本项目是集vue2.6+vuex+vue- router+Element Ul+ES6+webpack+axios+... 本项目以实战项目为切入点,模拟公司Vue项目研发流程,从头到尾讲解项目功能实现和优化、打包上线流程等,帮助提高你的技术层级,突破技术瓶颈。
  • VUE实战项目开发_中级

    2020-06-11 19:07:25
    本项目是集vue2.6+vuex+vue- router+Element Ul+ES6+webpack+axios+... 本项目以实战项目为切入点,模拟公司Vue项目研发流程,从头到尾讲解项目功能实现和优化、打包上线流程等,帮助提高你的技术层级,突破技术瓶颈。
  • 在公司做了几个项目,但都是写的业务代码,没有独自走过整体流程,这次跟着网上教程(Vue实战项目:电商管理系统(Element-UI)),提升一下整体能力 一、elementUI不支持vue3.0 跟着视频一顿操作,项目运行起来就...

    简单介绍一下:本人是UI转前端,有html,css基础,自学了js,vue。在公司做了几个项目,但都是写的业务代码,没有独自走过整体流程,这次跟着网上教程(Vue实战项目:电商管理系统(Element-UI)),提升一下整体能力

    一、elementUI不支持vue3.0

    跟着视频一顿操作,项目运行起来就遇到第一个坑,Cannot read property 'use' of undefined
    在这里插入图片描述
    找了一下网上资料,然后去elementUI官网一看,果然
    在这里插入图片描述
    信息收集时间为2020年10月21日11:27:04
    也就是说引入elementUI组件需要用到import Vue from 'vue'以及Vue.use()不过在vue3.0中是这样的import { createApp } from 'vue'找了不少资料不知道怎么解决,欢迎大佬评论解决,我这里的解决方案是,回退vue版本,哈哈,因为不知道后面还会有什么不兼容的地方。

    展开全文
  • Vue - 构建vue实战项目

    2021-01-10 13:38:58
    目录基础知识前后端分离页面的 url 构成vue框架安装nodejs、vue-cli等环境安装nodejs环境安装 vue-cli 脚手架工具用 vue-cli 构建一个项目项目文件介绍项目初始文件介绍配置src和static目录修改 App.vue、router和...

    基础知识

    前后端分离

    交互形式

    浏览器----(请求接口)-----服务器

    在前后端分离架构中,后端只需要负责按照约定的数据格式向前端提供可调用的API服务。前后端之间通过HTTP请求进行交互,前端获取到数据后,进行页面的组装和渲染,最终返回给浏览器。

    代码的组织方式

    代码的彻底分离:
    前后端代码库分离。前端代码中有可以进行Mock测试(通过构造虚拟测试对象以简化测试环境的方法)的伪后端,能支持前端的独立开发和测试。后端代码中除了功能实现外,还有着详细的测试用例,以保证API的可用性,降低集成风险。

    开发模式

    产品/领导/客户提出需求
    UI做出设计图
    前后端约定接口&数据&参数
    前后端并行开发(无强依赖,可前后端并行开发,如果需求变更,只要接口&参数不变,就不用两边都修改代码,开发效率高)
    前后端集成
    前端页面调整
    集成成功
    交付

    接口规范流程

    在开发期间前后端共同商定好数据接口的交互形式和数据格式。然后实现前后端的并行开发,这个步骤是前后端分离最为重要的。

    1. 前端工程师在开发完成之后可以独自进行mock测试;
    2. 后端也可以使用接口测试平台进行接口自测;
    3. 前后端一起进行功能联调并校验格式,最终进行自动化测试。

    页面的 url 构成

    http://www.baidu.com/login.html?wd=vue&ie=UTF-8#test/index

    如上的 url 由以下部分组成:

    • http:// 规定了页面采用的协议。
    • www.baidu.com/ 为页面所属的域名。
    • login.html为读取的文件名称。
    • ?wd=vue&ie=UTF-8 给页面通过 GET 方式传送的参数
    • #test/index 为页面的锚点区域

    前面四个发生改变的时候,会触发浏览器的跳转亦或是刷新行为,而更改 url 中的锚点,并不会出现这种行为,因此,几乎所有的 spa 应用都是利用锚点的这个特性来实现路由的转换。


    以 vue 项目为例,它的本地 url 结构一般为以下格式:

    http://localhost:3000/#/user/login
    路由地址是在 # 号后面的,也就是利用了锚点的特性。


    vue框架

    为了实现前后端分离的开发理念,开发前端 SPA 项目,实现数据绑定,路由配置,项目编译打包等一系列工作的技术框架。

    围绕 vue.js 配套的一系列的工具(包含了一整套外围工具的完整系统):

    • vue.js 核心
    • vue-router 实现路由工具
    • webpack 项目打包以及编译工具
    • nodejs 前端开发环境
    • npm 前端包管理器
    • axios ajax 接口请求工具
    • sass-loader 和 node-sass css 预处理
    • element-ui 基于 vue 的组件库
    • vue-cli vue 项目脚手架(一键安装 vue 全家桶的工具)

    安装nodejs、vue-cli等环境

    安装nodejs环境

    1. 安装nodejs环境,因为vue依赖nodejs环境

    nodejs 官方网站下载安装包: https://nodejs.org/

    1. 安装完成后,打开终端检查是否安装成功

    注意:新版本的nodejs内置npm,不需要独立安装。

    查看npm是否已经安装:npm —version
    在这里插入图片描述
    查看node版本号,如果正确显示版本号,则安装成功
    在这里插入图片描述


    安装 vue-cli 脚手架工具

    1. 在终端输入:
      npm install vue-cli -gcnpm install vue-cli -g

    最好安装cnpm淘宝镜像源:npm install cnpm -g
    原因:安装需要联网,如果npm被墙,可以使用cnpm安装;使用cnpm的安装速度更快

    • npm 是 nodejs 内置的官方包管理器。简单的理解为,可以用来管理所有的依赖包。
    • install 命令表示执行安装操作(uninstall、update等)。
    • vue-cli 是我们要安装的包。
    • -g 是命令参数,代表这个包将安装为系统全局的包。
    1. 安装完成后在终端输入命令行查询相关信息:

    在这里插入图片描述
    在这里插入图片描述
    3. 环境搭建好之后,就可以开始创建项目


    用 vue-cli 构建一个项目

    1. 创建项目文件夹,打开文件夹后,按shift+鼠标右键,选择‘在此处打开PowerShell窗口’,在终端输入:
      vue init webpack myproject
    • vue 安装的vue-cli脚手架的命令
    • init 初始化一个以webpack为模版、名字叫myproject的项目

    安装过程出现的提示信息:

    1. Project name (my-vue-project) --项目名称,如果不想改,直接回车
    2. Project description(A Vue.js project) --项目描述,如果不想改,直接回车
    3. Author (24439…) --项目作者,如果不想改,直接回车

    4. Vue build (Use arrow keys) --是否需要安装编译器,我们选择安装,直接回车
    5. Install vue-router (Y/n) --是否需要安装vue-router路由管理,我们选择安装,直接回车
    6. Use ESLint to lint your code? (Y/n) --安装eslint检查语法,我选择不安装,按n,再按回车(如果安装,按回车)
    7. Setup unit tests? (Y/n) --安装单元测试,我选择不安装,按n,再按回车
    8. Setup e2e tests with Nightwatch? (Y/n) --还是关于测试,我选择不安装,按n,再按回车
    9. 选择使用npm

    在这里插入图片描述

    1. 安装过程需要联网,等待安装
    2. 项目构建完成

    在这里插入图片描述
    在这里插入图片描述

    1. 按照提示,需要先进到myproject cd myproject + 回车
      为了保证项目中的依赖是完整的,需要先执行:cnpm install
      运行项目:cnpm run dev + 回车

    在这里插入图片描述

    1. 项目启动成功
      如果是手动启动项目,可以在浏览器输入:http://localhost:8080访问;
      如果想让浏览器自动打开,可以在build目录下的index.js文件中修改autoOpenBrowser: true,成true,重新运行就可以自动打开浏览器了。
      在这里插入图片描述
      在这里插入图片描述

    项目文件介绍

    项目初始文件介绍

    ├── node_modules                    # 项目依赖包文件夹
    ├── 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                  # 编译配置文件
    ├── src                              # 项目源码编写文件
    │   ├── App.vue                      # APP入口文件
    │   ├── assets                       # 初始项目资源目录,删除
    │   │   └── logo.png
    │   ├── components                   # 组件目录
    │   │   └── Hello.vue                # 测试组件,删除
    │   ├── main.js                      # 主配置文件
    │   └── router                       # 路由配置文件夹
    │       └── index.js                 # 路由配置文件
    └── static                           # 资源放置目录
    ├── index.html                       # 项目入口html模板
    ├── package.json                     # 项目依赖包配置文件
    ├── .babelrc                         # babel 配置
    ├── .postcssrc.js                    # postcss 配置
    ├── .editorconfig                    # editor 配置
    └── README.md                        # 项目说明文档
    

    实际开发的时候,主要操作 src 里面的文件,但需要我们重新整理里面的文件;static 资源目录,也需要根据放置不同的资源构建不同的子文件夹。


    配置src和static目录

    配置 src 目录,也可以根据自己的实际需求配置目录:

    ├── api                           // 接口调用工具文件夹
    │   └── index.js
    ├── components                    // 组件文件夹
    │    ├── header.vue
    │    └── footer.vue
    ├── page                          // 页面文件夹
    │   ├── content.vue               // 内容页面
    │   └── index.vue                 // 首页列表
    ├── router                        // 路由配置文件夹
    │   └── index.js
    ├── store                         // vuex状态管理目录
    ├── style                         // scss 样式存放目录
    │   └── style.scss                // 主样式文件,可以按分类创建多个文件夹
    └── utils                         // 常用工具文件夹
    │   └── index.js
    ├── App.vue                       // APP入口文件
    └── main.js                       // 项目配置文件
    

    .vue 文件,是一个自定义的文件类型,用类 HTML 语法描述一个 Vue 组件。

    每个 .vue文件包含三种类型的顶级语言块 <template>, <script> 和 <style>。
    这三个部分分别代表了 html,js,css。

    配置static目录:

    ├── css               # 放第三方的样式文件
    ├── font              # 放字体图标文件
    ├── image             # 放图片文件,里面可以根据图片种类创建文件夹
    └── js                # 放第三方的JS文件,比如datepicker
    

    src 目录里面已经包含的样式和JS与 static 文件里面的区别:

    如果放在 src 目录里面,则每次打包的时候都需要打包。static里面的文件,一般不会去修改,也没必要 npm 安装,可以直接引用。


    修改 App.vue、router和page 文件

    修改 App.vue 文件

    <template>
      <div id="app">
        <router-view/>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
    <style lang="less" scoped>
    </style>
    

    这里需要引入less文件,否则报错。
    npm install --save less less-loader
    在这里插入图片描述
    安装成功后,检查版本
    在这里插入图片描述
    安装less依赖后项目打不开的话,可以选择安装低版本的less依赖。


    修改router/index.js文件

    import Vue from 'vue'
    import Router from 'vue-router'
    // import HelloWorld from '@/components/HelloWorld'   // 可删除
    import index from '../components/index.vue'
    import content from '../components/content.vue'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          // name: 'HelloWorld',   path别名,可删除
          name: '首页',
          meta: { // 设置页面title
          	title: '首页'
          },
          // component: index
          component: content
        },{ // 详情页
          path: '/content/:id',
          name: '详情',
          meta: {
            title: '详情'
          },
          component: content
        }
      ]
    })
    
    

    path:‘/’ 就是默认首页,也就是index页面。
    这里’/content/:id’使用了动态路由匹配, :id 就是各内容页面的ID, 例如‘/content/10’、‘/content/20’这种。


    添加 index.vue 和 content.vue 文件

    • index.vue 文件
    <template>
      <div>index</div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style>
    
    </style>
    
    • content.vue 文件
    <template>
        <div>content</div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style>
    
    </style>
    

    运行结果如下:
    在这里插入图片描述
    在这里插入图片描述


    配置 axios api 接口

    安装axios

    cnpm install axios --save
    在这里插入图片描述


    调整 main.js 文件

    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import axios from 'axios'
    
    Vue.config.productionTip = false
    
    // 全局注册axios
    Vue.prototype.$http = axios
    
    /* eslint-disable no-new */
    new Vue({
        el: '#app',
        router,
        components: { App },
        template: '<App/>'
    })
    

    全局注册后,就可以在其他页面按照this.$http这样的方式调用axios方法,不必每个页面引入axios


    调整src/index.vue 文件

    <template>
      <div>index</div>
    </template>
    
    <script>
    export default {
        mounted() {
            this.$http.get('https://unpkg.com/axios@0.18.0/dist/axios.min.js')
            .then(res => {
                console.log(res);
            })
        }
    }
    </script>
    
    <style>
    
    </style>
    

    在这里插入图片描述

    这里调用的接口是https://unpkg.com/axios@0.18.0/dist/axios.min.js,返回的结果就是axios的源码。

    展开全文
  • 分享一个vue 3.0从基础入门到实战项目
  • 高仿网易云音乐(Vue实战项目) ???? 仅供学习使用开发的高仿网易云音乐的完整项目~! 仅供个人学习~! 群号:970804317 下面也会有二维码 请务必将文档看完, 很多问题都可以在文档中找到解决,也可以加入群一起交流, B站...
  • 在上一节《vue+vue-router+axios+element-ui构建vue实战项目之七(渲染content.vue内容)》中,我们成功渲染出了内容页面。 不过,我们还漏掉了一个重要的东西,不知道大家还记得《vue+vue-router+axios+element-ui...
  • 在上一篇《vue+vue-router+axios+element-ui构建vue实战项目之二(nodejs、vue-cli等环境安装)》中,我们通过安装nodejs系统环境,以及vue-cli脚手架工具,在执行完命令后,我们就已经将一个初始项目跑起来了。...
  • 硅谷外卖视频、代码及笔记的百度云盘地址,老师讲的很好!
  • 开个新坑玩下,如果新项目不赶的话应该会每周持续更新,断更就当我没说,(≧▽≦)/目前技术栈:electron、vue、elementui、koa主题:未定(金融类、图书类)首先是环境构建npm install -g vue-cli vue init ...
  • vue_shop 项目设置 npm install 编译和热重装以进行开发 npm run serve 编译并最小化生产 npm run build 整理和修复文件 npm run lint 自定义配置 请参阅。
  • springcloud+vue实战项目

    2018-12-25 13:06:14
    基于springcloud+vu构建的项目 用maven多模块的方式,下载下来安装maven环境 即可运行
  • vue实战项目视频地址以及项目文件一、项目概述1.1电商项目基本业务概述1.2电商后台管理系统的功能1.3电商后台管理系统的开发模式(前后端分离)1.4电商后台管理系统的技术选型1.4.1前端项目技术栈1.4.2后端项目技术...
  • 初级项目vue实战项目 收银系统

    千次阅读 2018-06-12 16:56:15
    Vue实战视频-快餐店收银系统 (共11集)2017-05-22 分类:Vue.js视频教程 / 视频教程 阅读(126832) 评论(136) 课程前言 这是我网站恢复后写的第一篇文章,在关站这段时间里,群里的小伙伴们给了我很多支持,...
  • vue实战项目总结

    2018-07-01 18:08:43
    vue项目总结 实际项目中经常会遇到会有奇数行偶数行赋予不同样式的请求。可以根据v-for与v-bind:class进行实现 &amp;amp;amp;amp;amp;lt;div v-for=&amp;amp;amp;amp;quot;(item,index) in items&...
  • 下面是我在模仿 Vue实战项目:电商管理系统 时遇到的问题。 我跟着视频中的操作,很顺利地持续到了接口测试。 在此之前我已经把数据库导进了我的电脑phpstudy的mysql文件夹中。在我用postman测试接口...
  • Vue实战项目个人总结

    2020-05-06 16:25:40
    开发项目流程 1.项目需求分析 2.项目工期评估 3.项目责任划分 前端: 静态页面制作 前端框架选型(选择vue之类的框架) 前端页面架构(文件的划分,架构的模式等等) 后端: 数据库开发 API接口文档 API接口实现 ...
  • 2019全新打造Web前端教程,Vue实战项目之喵喵电影,详细讲解项目演示与开发流程。
  • 本次更新web前端第二十三阶段Ego商城高级Vue实战项目课程及课程资料。第二十三阶段:Ego商城高级Vue实战项目章节1:Ego商城高级Vue实战项目1:Ego-项目介绍2:Ego-项目基本配置3:Ego-导航栏4:Ego-登陆判断5:Ego-...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,391
精华内容 3,356
关键字:

vue实战项目

vue 订阅