1 安装View UI组件
1.1 什么是View UI
后台管理信息系统(MIS)是软件开发的一个重要领域,如OA、ERP、商城后台等等都属于MIS系统。业务人员需要在MIS系统中操作大量的表单和数据,传统的服务器(同步)页面伴随着大量刷新,用户体验很差,所以开发界喜欢选择以vue为代表的新一代前后端分离技术以实现流程的操作。
在MIS系统开发中,需要大量的表单、表格、日历、选项卡等复杂组件来完成业务功能,这些组件实现起来都比较复杂,作为普通程序员,一般会选择现成的商业组件。
业界比较成熟的后台商业组件主要有:Element UI、View UI 和 Ant Design,这些组件的功能和使用方式都大同小异,这里介绍View UI给大家使用。
官网:iView - A high quality UI Toolkit based on Vue.js
View UI的前身是iView,至今已经是4.0版,由于最新版引入了商业模式,因此代码更新较快。
1.2 安装View UI
官方的安装教程:https://www.iviewui.com/docs/guide/install
为vue项目安装View UI组件有很多方式,这里使用最简便得方法,就是直接使用vue-cli得ui向导来完成安装。新版得vue-cli不仅可以通过命令行来完成项目创建,还可以通过web可视化方式创建,View UI可以作为vue-cli的插件添加到项目中。
(1)使用vue-cli可视化项目管理器
在命令行中输入以下指令:
代码语言:javascript复制vue ui
(2)添加插件:axios 和 view-ui
完成上述操作后,一个包含view-ui插件库的vue工程就创建好了,正常进入项目目录执行:
代码语言:javascript复制npm run serve
2 项目布局:
2.1 栅格系统
类似BootStrap中的12栅格系统,View UI通用把页面分为行(Row)和列(Col),使用24栅格进行布局。
官方的栅格教程:https://www.iviewui.com/components/grid
2.2 设置路由:
(1)分层次创建组件
(2)设置父子级别路由:
代码语言:javascript复制const routes = [
2
{
3
path: '/',
4
name: 'home',
5
component: Home,
6
children:[
7
{
8
path:'/',
9
name:'default',
10
component:Default,
11
meta:{
12
title: '后台首页'
13
}
14
},
15
{
16
path:'/categories',
17
name:'categories',
18
component:Categories,
19
meta:{
20
title: '分类管理'
21
}
22
},
23
{
24
path:'/products',
25
name:'products',
26
component:Products,
27
meta:{
28
title: '商品管理'
29
}
30
},
31
]
32
},
33
{
34
path:'/login',
35
name:'login',
36
component:Login
37
}
38
]
2.3 布局
业务系统通常由比较严谨的布局,View UI为我们准备好了多种布局风格,这里使用“顶部-侧边布局”作为示例:
https://www.iviewui.com/components/layout#DB-CBBJ
代码语言:javascript复制<style scoped>
2
......
3
.layout-logo{
4
......
5
color:#fff;
6
line-height: 30px;
7
text-align: center;
8
}
9
......
10
</style>
11
<template>
12
<div class="layout">
13
<Layout>
14
<Header>
15
<Menu mode="horizontal" theme="dark" active-name="1">
16
<div class="layout-logo">
17
<router-link to="/"><h3>趣物网 - 后台管理</h3></router-link>
18
</div>
19
<div class="layout-nav">
20
<MenuItem name="1">
21
<Icon type="ios-navigate"></Icon>
22
Item 1
23
</MenuItem>
24
<MenuItem name="2">
25
<Icon type="ios-keypad"></Icon>
26
Item 2
27
</MenuItem>
28
<MenuItem name="3">
29
<Icon type="ios-analytics"></Icon>
30
Item 3
31
</MenuItem>
32
<MenuItem name="4">
33
<Icon type="ios-paper"></Icon>
34
Item 4
35
</MenuItem>
36
</div>
37
</Menu>
38
</Header>
39
<Layout :style="{padding: '0 50px'}">
40
<Breadcrumb :style="{margin: '16px 0'}">
41
<BreadcrumbItem>后台管理</BreadcrumbItem>
42
<BreadcrumbItem>{{$route.meta.title}}</BreadcrumbItem>
43
</Breadcrumb>
44
<Content :style="{padding: '24px 0', minHeight: '280px', background: '#fff'}">
45
<Layout>
46
<Sider hide-trigger :style="{background: '#fff'}">
47
<Menu active-name="1-2" theme="light" width="auto" :open-names="['1']">
48
<Submenu name="1">
49
<template slot="title">
50
<Icon type="ios-navigate"></Icon>
51
商品信息管理
52
</template>
53
<MenuItem name="1-1">
54
<router-link to="/categories">分类管理</router-link>
55
</MenuItem>
56
<MenuItem name="1-2">
57
<router-link to="/products">商品管理</router-link>
58
</MenuItem>
59
</Submenu>
60
<Submenu name="2">
61
<template slot="title">
62
<Icon type="ios-analytics"></Icon>
63
客户订单管理
64
</template>
65
</Submenu>
66
</Menu>
67
</Sider>
68
<Content :style="{padding: '24px', minHeight: '280px', background: '#fff'}">
69
<router-view></router-view>
70
</Content>
71
</Layout>
72
</Content>
73
</Layout>
74
<Footer class="layout-footer-center">2011-2016 © TalkingData</Footer>
75
</Layout>
76
</div>
77
</template>
78
<script>
79
export default {}
80
</script>
3 常见组件的使用:
3.1 Table - 数据表格
表格组件通过columns属性绑定列,通过data属性绑定行数据。以分类管理组件(Categories.vue)为例:
代码语言:javascript复制 <Table border :columns="columns" :data="categories">
2
<template slot="operation" slot-scope="{row}">
3
<Button type="primary" @click="showEdit(row)">修改</Button>
4
<Button type="error" @click="deleteCategory(row)">删除</Button>
5
</template>
6
</Table>
背后绑定的数据:
代码语言:javascript复制export default {
2
name:'categories',
3
data(){
4
return {
5
columns:[
6
{title:'分类ID', key:'id'},
7
{title:'分类名称', key:'name'},
8
{title:'操作', slot:'operation', align: 'center'}
9
],
10
categories:[]
11
}
12
},
其中 coloumns中每一个对象代表一个列,title是列标题,key是该列绑定的对象属性名。
如果列中由其它组件组成,则可以定义为插槽(slot),让后再通过Table组件中的模板(template)去定制slot中的结构。
template中的slot属性需要和columns中对用列的slot属性向对应,template中的slot-scope则用于定义Table向slot中传入的上下文数据。
3.2 Form - 表单组件
表单组件可以绑定数据和数据校验。以登录组件(Login.vue)为例:
代码语言:javascript复制<template>
2
<div>
3
<h1 class="title">趣物网-登录</h1>
4
<Row>
5
<Col span="8" offset="8">
6
<Card>
7
<p slot="title">
8
<Icon type="ios-film-outline"></Icon>用户登录
9
</p>
10
<Form ref="loginForm" :model="user" :rules="ruleValidate" :label-width="80">
11
<FormItem label="用户名" prop="username">
12
<Input v-model="user.username" placeholder="用户名..." />
13
</FormItem>
14
<FormItem label="密码" prop="password">
15
<Input type="password" v-model="user.password" placeholder="密码..." />
16
</FormItem>
17
<FormItem>
18
<Button @click="login" type="primary">登录</Button>
19
</FormItem>
20
</Form>
21
</Card>
22
</Col>
23
</Row>
24
</div>
25
</template>
(1)Form的 :model="user" 用于设置绑定对象,:rules="ruleValidate" 用于设置绑定验证;
(2)其中FormItem的 prop="username" 用于指定当前项需要验证的属性名,即ruleValidate中的属性名;
(3)为了方便调用验证,我们使用 ref="loginForm" 为表单对象设置了引用名,于是下面的代码可以通过 “this.$refs['loginForm'].validate( (valid)=>{...} )” 来显式调用表单验证。
代码语言:javascript复制<script>
2
import UserInfo from '@/js/UserInfo.js'
3
const userInfo = new UserInfo();
4
5
export default {
6
name:'login',
7
data(){
8
return{
9
user:{
10
username:'',
11
password:''
12
},
13
ruleValidate:{
14
username:[{required:true, message:'请填写用户名', trigger:'blur'}],
15
password:[{required:true, message:'请填写密码', trigger:'blur'}],
16
}
17
}
18
},
19
methods:{
20
login(){
21
this.$refs['loginForm'].validate((valid)=>{
22
if(valid){
23
this.axios.post('/api/auth/login', this.user).then(res=>{
24
if(res.data.roleName!='管理员'){
25
alert('您不是管理员,无法进入后台');
26
}else{
27
userInfo.saveLoginUser(res.data);
28
let redirect ='/'
29
if(this.$route.query.redirect){
30
redirect = this.$route.query.redirect;
31
}
32
this.$router.push({path: redirect});
33
}
34
35
}).catch(()=>alert('用户名或密码有误'));
36
}
37
});
38
}
39
}
40
}
41
</script>
3.3 Modal - 模态框
模态框可以通过简单的布尔属性绑定实现显示和隐藏。继续实现分类管理(Categories.vue)中的分类信息编辑功能:
代码语言:javascript复制<Modal
2
v-model="showModal"
3
title="商品分类编辑">
4
<Form ref="categoryForm" :model="editCategory" :rules="editValidate" :label-width="100">
5
<input type="hidden" v-model="editCategory.id" />
6
<FormItem label="分类名称" prop="name">
7
<Input v-model="editCategory.name" placeholder="分类名称..." />
8
</FormItem>
9
</Form>
10
<div slot="footer">
11
<Button @click="showModal=false">取消</Button>
12
<Button type="primary" @click="saveCategory">保存</Button>
13
</div>
14
</Modal>
以下是分类编辑的代码,值得注意的是:
vue是支持双向绑定的,如果编辑对象是既显示在Table中,又可以被Form元素修改,则会产生联动问题,即使最终放弃了Form中的变更,也会导致Table中的数据发生变化,因此需要克隆一份数据副本进行修改。
代码语言:javascript复制export default {
2
name:'categories',
3
data(){
4
return {
5
......
6
showModal: false,
7
editCategory:{},
8
editValidate:{
9
name:[{required:true, message:'请填写分类名称', trigger:'blur'}],
10
}
11
}
12
},
13
methods:{
14
......
15
saveCategory(){ //保存编辑信息
16
this.$refs['categoryForm'].validate( valid=>{
17
if(valid){
18
this.axios.post('/api/admin/categories', this.editCategory).then(()=>{
19
this.editCategory = {};
20
this.showModal = false;
21
this.loadCategories();
22
});
23
}
24
});
25
},
26
showEdit(item){ //弹出编辑框并传入编辑数据
27
if(item){
28
this.editCategory = JSON.parse(JSON.stringify(item)); //克隆副本再做绑定
29
}else{
30
this.editCategory = {};
31
}
32
this.showModal = true;
33
}
34
}
35
}
4 客户端权限:为路由设置拦截器
为了避免未登陆用户能访问后台页面,我们需要为后台路由设置守卫(拦截器)。利用router中的beforeEach事件钩子,我们可以添加守卫。
(1)在程序入口router/index.js中添加路由钩子
代码语言:javascript复制import UserInfo from '@/js/UserInfo.js'
2
const userInfo = new UserInfo()
3
......
4
//设置路由拦截
5
router.beforeEach((to, from, next) => {
6
if (to.meta.anonymous) { // 判断该路由是否允许匿名访问
7
next();
8
}
9
else {
10
if (userInfo.isLogin()) { // 检查是否已登录,已登录继续
11
next();
12
}
13
else {
14
next({
15
path: '/login',
16
query: {redirect: to.fullPath} // 将跳转的路由path作为参数,保留被拦截路径URL
17
})
18
}
19
}
20
});
(2)在路由设置router.js中,允许匿名访问的路由项(比如 "/login"),添加meta自定义属性标识(比如"anonymous:true")
代码语言:javascript复制 const routes = [
2
......
3
{
4
path:'/login',
5
name:'login',
6
component:Login,
7
meta:{
8
anonymous:true //添加标识符,允许匿名
9
}
10
}
11
]
附:解决eslint语法报错“Parsing error: x-invalid-end-tag”问题。
问题原因:vue将标签渲染为原生html标签时,由于这些标签是自闭合的,所以有end标签会报错。
解决办法:在“.eslintrc.js” 配置文件的rules配置节中添加“'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }]”
代码语言:javascript复制module.exports = {
2
......
3
rules: {
4
......
5
'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }]
6
},
7
}
8