cmdb前端vue整体框架
目录:
使用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"
},
页面布局
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>
© 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>