业务后台商业组件ViewUI(iView)入门

2022-11-15 13:23:54 浏览数 (1)

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 &copy; 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>&nbsp;
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

0 人点赞