cmdb前端vue整体框架

时间:July 2, 2019 分类:

目录:

使用npm构建vue项目

安装vue

确保npm版本大于3.0

$ npm -v
6.4.1

安装vue和vuecli

# 升级或安装cnpm
$ npm install cnpm -g
# 安装vue
$ cnpm install vue
# 安装vue-cli
$ cnpm install --global vue-cli

命令行工具使用

创建项目

创建一个基于webpack的项目

$ vue init webpack my-project
? Project name my-project
? Project description A Vue.js project
? Author wanghongyua <why@whysdomain.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm

   vue-cli · Generated "my-project".


# Installing project dependencies ...
# ========================

npm WARN deprecated browserslist@2.11.3: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features!
npm WARN deprecated flatten@1.0.2: I wrote this module a very long time ago; you should use something else.
npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.

> core-js@2.6.9 postinstall /root/my-project/node_modules/core-js
> node scripts/postinstall || echo "ignore"

Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!

The project needs your help! Please consider supporting of core-js on Open Collective or Patreon: 
> https://opencollective.com/core-js 
> https://www.patreon.com/zloirock 

Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)


> uglifyjs-webpack-plugin@0.4.6 postinstall /root/my-project/node_modules/webpack/node_modules/uglifyjs-webpack-plugin
> node lib/post_install.js

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN ajv-keywords@3.4.1 requires a peer of ajv@^6.9.1 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

added 1208 packages from 667 contributors in 58.888s

# Project initialization finished!
# ========================

To get started:

  cd my-project
  npm run dev

Documentation can be found at https://vuejs-templates.github.io/webpack
$ cd my-project
$ cnpm install
$ cnpm run dev

目录结构

目录 用途
build 项目构建webpack的代码
config 配置目录
node_modules npm加载的模块
src 开发目录,包括几个目录和文件,assets用于放置图片例如logo,commponents放置组件文件,App.vue项目的入口文件,main.js项目的核心文件
static 静态资源目录
test 测试目录,可以删除
index.html 首页入口html
package.json 项目配置文件

启动服务

可能需要修改一下config/index.js,将host改为0.0.0.0,然后启动服务

$ npm install
$ npm run dev

可以通过浏览器访问看到

代码编写

index.html文件

默认代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="shortcut icon" href="favicon.ico" />
    <title>my-project</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

需要修改的可能是title和metadat等信息了

src/App.vue文件

默认代码

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
  </div>
</template>

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

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

引入一个layout布局组件

<template>
  <div id="app">
    <layout></layout>
  </div>
</template>

<script>
import layout from '@/components/Layout.vue'
export default {
  name: 'App',
  components: {
    'layout': layout
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  //text-align: center;
  color: #2c3e50;
  //margin-top: 60px;
}
</style>

布局计划采用Element-UI实现

main.js

main.js是入口js

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

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

在这里可以

引入包

import store from './store';
import axios from 'axios';
import ElementUI from 'element-ui';

使用外部组件

这边整体UI部分采用ElementUI

Vue.use(ElementUI);

定义全局变量

使用axios进行ajax请求,定义为http全局变量

Vue.prototype.$http = axios;

引入css

import '../static/css/theme/index.css';
import '../static/css/fontawesome.min.css';

路由前校验

// 路由前进行匹配
router.beforeEach((to, from, next) => {
  console.log(store.state.loggedIn);
  console.log(to);
  console.log(from.path);
  if (to.name === 'Login') {
    next();
  } else {
    // 登录状态下
    if (store.state.loggedIn) {
      if (to.name === 'PasswordChange') {
        next()
        // 获取是否有权限 
      } else if (store.state.permissions.indexOf(store.state.paths[to.name]) > -1) {
        console.log('permission verified');
        next();
      } else {
        console.log('permission denied');
        next({path: '/login'});
      }
    } else {
      axios.get('/api/account/login/').then(function (response) {
        store.dispatch('setLoggedIn', response.data.loggedIn);
        if (store.state.loggedIn) {
          store.dispatch('setUser', response.data.user);
          store.dispatch('setPermissions', response.data.permissions);
          next();
        } else {
          next({path: '/login'});
        }
      }).catch(function (error) {
        next({path: '/login'});
      });
    }
  }
})

beforeEach的三个参数含义

  • to: router即将进入的路由对象
  • from: 当前导航即将离开的路由
  • next: Function, 进行管道中的一个钩子,如果执行完了,则导航的状态就是confirmed(确认的);否则为false,终止导航

在app组件引入

new Vue({
  el: '#app',
  router,
  store,    //  new
  components: { App },
  template: '<App/>'
})

store前端存储

src/store.js

import Vue from 'vue'
import Vuex from 'vuex'

// 引入vuex组件
Vue.use(Vuex)
// 数据
const state = {
  user: {},
  loggedIn: false,
  permissions: [],
  bns: [],
  paths: {
    'UserManagement': 'account.view_user',
    'GroupManagement': 'auth.change_group',
    'PermissionManagement': 'auth.change_permission',
    'Dashboard': 'account.view_user'
  },
  datePickerOptions: {
    shortcuts: [
      {
        text: '今天',
        onClick (picker) {
          picker.$emit('pick', new Date())
        }
      },
      {
        text: '昨天',
        onClick (picker) {
          const date = new Date()
          date.setTime(date.getTime() - 3600 * 1000 * 24)
          picker.$emit('pick', date)
        }
      },
      {
        text: '一周前',
        onClick (picker) {
          const date = new Date()
          date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
          picker.$emit('pick', date)
        }
      }
    ]
  },
  dateRangePickerOptions: {
    shortcuts: [{
      text: '最近一周',
      onClick (picker) {
        const end = new Date()
        const start = new Date()
        start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
        picker.$emit('pick', [start, end])
      }
    },
    {
      text: '最近一个月',
      onClick (picker) {
        const end = new Date()
        const start = new Date()
        start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
        picker.$emit('pick', [start, end])
      }
    },
    {
      text: '最近三个月',
      onClick (picker) {
        const end = new Date()
        const start = new Date()
        start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
        picker.$emit('pick', [start, end])
      }
    }]
  }
}

// store数据更新方法
const mutations = {
  setUser (state, user) {
    state.user = user
  },
  setLoggedIn (state, loggedIn) {
    state.loggedIn = loggedIn
  },
  setPermissions (state, permissions) {
    state.permissions = permissions
  },
  setBns (state, bns) {
    state.bns = bns
  }
}

// 刷新页面数据
const actions = {
  setUser (context, user) {
    context.commit('setUser', user)
  },
  setLoggedIn (context, loggedIn) {
    context.commit('setLoggedIn', loggedIn)
  },
  setPermissions (context, permissions) {
    context.commit('setPermissions', permissions)
  },
  setBns (context, bns) {
    context.commit('setBns', bns)
  }
}

// 创建vuex实例
export default new Vuex.Store({
  state,
  mutations,
  actions
})

配置路由

src/router/index.js

默认的配置

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

Vue.use(Router)

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

添加需要的路由

import Vue from 'vue'
import Router from 'vue-router'
import Dashboard from '@/components/Dashboard'
import Login from '@/components/Login'
import PasswordChange from '@/components/PasswordChange'
import UserManagement from '@/components/UserManagement'
import GroupManagement from '@/components/GroupManagement'
import PermissionManagement from '@/components/PermissionManagement'
Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Dashboard',
      component: Dashboard
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    {
      path: '/passwordChange',
      name: 'PasswordChange',
      component: PasswordChange
    },
    {
      path: '/userManagement',
      name: 'UserManagement',
      component: UserManagement
    },
    {
      path: '/groupManagement',
      name: 'GroupManagement',
      component: GroupManagement
    },
    {
      path: '/permissionManagement',
      name: 'PermissionManagement',
      component: PermissionManagement
    }
  ]
})

依赖管理

package.json

  "dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "element-ui": "^2.3.4",
    "vuex": "^3.0.1",
    "axios": "^0.18.0",
    "moment": "^2.22.1"
  },

页面布局

参考Element-UI的container

src/components/Layout.vue

<template>
  <el-container>
    <!-- 左侧边栏,使用navigation组件 -->
    <el-aside :width="width">
      <navigation :collapse="collapse"></navigation>
    </el-aside>
    <!-- 右侧 -->
    <el-container>
      <!-- 右侧顶部 -->
      <el-header height="40px">
        <div class="layout-header">
          // header左侧,按钮,绑定方法用于收起左侧边栏
          <div class="layout-nav-button">
            <el-button size="large" type="text" @click="hideNavbar">
              <i :class="{'fas fa-indent fa-lg': collapse, 'fas fa-outdent fa-lg': !collapse}"></i>
            </el-button>
          </div>
          <!--header中间-->
          <div class="layout-breadcrumb">
            <el-breadcrumb>
              <el-breadcrumb-item v-for="(node, index) in bns" :key="index">{{ node }}</el-breadcrumb-item>
            </el-breadcrumb>
          </div>
         <!--header右侧-->
          <div class="layout-ceiling">
            <a href="http://www.whysdomain.com/issues">需求反馈</a> |
            <a href="http://www.whysdomain.com/help">帮助中心</a>
            <div class="layout-user">
              <el-dropdown @command="handleCommand">
                <!--显示-->
                <span class="el-dropdown-link">
                  <i class="fas fa-lg fa-user-circle"></i>
                    {{ this.$store.state.user.fullname }}
                  <i class="el-icon-arrow-down el-icon--right"></i>
                </span>
                <!--修改密码-->
                <el-dropdown-menu slot="dropdown">
                  <el-dropdown-item command="/passwordChange" :disabled="!$store.state.loggedIn">
                    <span>
                      <i class="fas fa-key fa-lg"></i> 修改密码
                    </span>
                  </el-dropdown-item>
                  <el-dropdown-item v-if="$store.state.loggedIn" command="/api/account/logout/">
                    <span>
                      <i class="fas fa-sign-out-alt fa-lg"></i> 退出
                    </span>
                  </el-dropdown-item>
                </el-dropdown-menu>
              </el-dropdown>
            </div>
          </div>
        </div>
      </el-header>
      <!-- 中间,使用router-view组件进行展示 -->
      <el-main><router-view></router-view></el-main>
      <el-footer>
        &copy; 2017-{{ year }} whysdomain.comcom
        <a href="http://cmdb.whysdomain.com">SRE</a>
      </el-footer>
    </el-container>
  </el-container>
</template>
<script>
  // 引入moment时间格式化组件
  import moment from 'moment';
  // 左侧边栏组件
  import navigation from '@/components/Navigation.vue';
  export default {
    components: {
      'navigation': navigation
    },
    data: function () {
      return {
        collapse: false,
        width: '200px',
        bns: this.$store.state.bns,
        year: ''
      };
    },
    methods: {
      // 设置进行隐藏并且设置cookie 
      hideNavbar: function () {
        if (this.collapse) {
          this.clearCookie();
          this.setCookie('collapse', false, 7);
          this.width = '200px';
          this.collapse = false;
        } else {
          this.clearCookie();
          this.setCookie('collapse', true, 7);
          this.width = '80px';
          this.collapse = true;
        }
      },
      // 用于跳转的方法
      handleCommand: function (url) {
        if (url === '/passwordChange') {
          this.$router.push({path: url});
        } else if (url === '/api/account/logout/') {
          var _this = this;
          this.$http.get(url).then(function (response) {
            _this.$store.dispatch('setLoggedIn', response.data.loggedIn);
            _this.$message({message: '成功退出, 即将跳转到登陆页面', type: 'success'});
            _this.$router.push({path: '/login'});
          }).catch(function (error) {
            _this.$message.error('登出发生错误,请联系管理员');
          });
        }
      },
      // 清空cookie的collapse
      clearCookie: function () {
        this.setCookie('collapse', '', -1);
      },
      // 设置cookie,时间为7天
      setCookie: function (cname, cvalue, exdays) {
        var d = new Date();
        d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
        var expires = "expires=" + d.toUTCString();
        //console.info(cname + "=" + cvalue + "; " + "path=/; " + expires);
        document.cookie = cname + "=" + cvalue + "; " + "path=/; " + expires;
        //console.info(document.cookie);
      },
      // 获取cookie
      getCookie: function (cname) {
        var name = cname + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
          var c = ca[i];
          while (c.charAt(0) == ' ') c = c.substring(1);
            if (c.indexOf(name) != -1) return c.substring(name.length, c.length);
        }
        return "";
      },
      // 设置左侧边栏
      setCollapse: function () {
        if (this.getCookie('collapse') === 'true') {
          this.width = '80px';
          this.collapse = true;
        } else {
          this.width = '200px';
          this.collapse = false;
        }
      }
    },
    // 加载数据
    mounted: function () {
      this.setCollapse();
      this.year = moment().format('YYYY');
    }
  }
</script>
<style>
  .div-header {
    margin: 5px;
  }
  .el-col {
    margin-top: 5px;
    margin-bottom: 5px;
  }
</style>
<style scoped>
  .el-header {
    height: 40px;
    padding: 0 5px;
  }
  .layout-header {
      height: 40px;
      background: #E5E9F2;
      margin: 1px;
      border-radius: 4px;
      box-shadow: 0 1px 1px rgba(0,0,0,.1);
  }
  .layout-nav-button {
      float: left;
      margin-left: 5px;
  }
  .layout-breadcrumb {
      float: left;
      margin: 13px;
      color: #EFF2F7;
  }
  .layout-ceiling {
      float: right;
      color: #D3DCE6;
      margin: 10px;
      overflow: hidden;
  }
  .layout-user {
      float: right;
      margin-left: 15px;
      margin-right: 25px;
      margin-top: 3px;
  }
  .el-aside {
    background-color: #2F4F4F;
  }
  .el-main {
    padding: 0 5px;
  }
  .el-footer {
    background-color: #E4E7ED;
    color: #333;
    text-align: center;
    line-height: 60px;
    padding: 0 5px;
  }
  body > .el-container {
    margin-bottom: 40px;
  }

  .el-container:nth-child(5) .el-aside,
  .el-container:nth-child(6) .el-aside {
    line-height: 260px;
  }

  .el-container:nth-child(7) .el-aside {
    line-height: 320px;
  }
</style>

左侧边栏

src/components/Navigation.vue

<template>
  <el-menu default-active="/" :default-openeds="['account', ]" class="el-menu-vertical" :router="true" background-color="#2F4F4F"
    text-color="#FFFFFF" active-text-color="#00B2EE" :collapse="collapse">
      <!-- 默认展开default-openeds -->
      <!-- router根据index进行跳转 -->
      <el-menu-item index="/">
        <i class="fas fa-home fa-lg"></i>
        <span slot="title">CMDB</span>
      </el-menu-item>
      <el-submenu index="account">
        <template slot="title">
          <i class="fas fa-user-secret fa-lg"></i>
          <span>用户&权限</span>
        </template>
        <el-menu-item index="/userManagement">
          <i class="fas fa-id-card fa-lg"></i>
          <span slot="title">用户中心</span>
        </el-menu-item>
        <el-menu-item index="/groupManagement">
          <i class="fas fa-users fa-lg"></i>
          <span slot="title">用户组</span>
        </el-menu-item>
        <el-menu-item index="/permissionManagement">
          <i class="fas fa-universal-access fa-lg"></i>
        <span slot="title">权限管理</span>
      </el-menu-item>
    </el-submenu>
  </el-menu>
</template>

<script>
  export default {
    // 获取父组件的参数
    props: ['collapse'],
    data: function () {
      return {
        path: '/'
      }
    }
  }
</script>