Vue从零开始(3):创建页面+添加路由+Mock模拟数据

6 minute read

前言

本来这篇想讲讲怎么打包项目的,但是刚好最近在做一个后台维护的前端项目,然后想想打包这部分可以往后放一放,可以先通过这个项目讲一讲怎么创建页面、添加路由然后如何使用Mock模拟数据。

上一篇大家对于Vue CLI搭建脚手架有了一些了解,这篇介绍一款比较容易上手的企业级中后台前端/设计解决方案Ant Design Pro。它基于Ant Design Vue并提供了一些常用的模板、组件,可以帮助你快速搭建企业级中后台产品原型。

创建项目

创建一个名为my-project的项目:

$ git clone --depth=1 https://github.com/sendya/ant-design-pro-vue.git my-project

目录结构

├── public
│   └── logo.png             # LOGO
|   └── index.html           # Vue 入口模板
├── src
│   ├── api                  # Api ajax 等
│   ├── assets               # 本地静态资源
│   ├── config               # 项目基础配置,包含路由,全局设置
│   ├── components           # 业务通用组件
│   ├── core                 # 项目引导, 全局配置初始化,依赖包引入等
│   ├── router               # Vue-Router
│   ├── store                # Vuex
│   ├── utils                # 工具库
│   ├── locales              # 国际化资源
│   ├── views                # 业务页面入口和常用模板
│   ├── App.vue              # Vue 模板入口
│   └── main.js              # Vue 入口 JS
│   └── permission.js        # 路由守卫(路由权限控制)
├── tests                    # 测试工具
├── README.md
└── package.json

本地开发

  1. 安装依赖:

    $ npm i
    
  2. 启动项目:

    $ npm run serve
    
  3. 浏览器访问http://localhost:8000

创建页面

先上一张原型图:

功能比较简单,全凭自己想象,在src/views/目录下面创建install文件夹,用于存放安装部分所有页面,然后在install文件夹下新建一个vue文件:DynamicData.vue(动态数据概览页面),然后写一大坨代码:

<template>
  <div>
    <a-card :bordered="false" title="历年安装电梯总台数">
      <div class="table-page-search-wrapper">
        <a-form layout="inline">
          <a-row :gutter="48">
            <a-col :md="8" :sm="24">
              <a-form-item label="年份">
                <a-select style="width: 100%;">
                  <a-select-option value="2019">
                    2019
                  </a-select-option>
                  <a-select-option value="2020">
                    2020
                  </a-select-option>
                </a-select>
              </a-form-item>
            </a-col>
            <a-col :md="16" :sm="24">
              <span class="table-page-search-submitButtons" style="float: right; overflow: hidden;">
                <a-button>重置</a-button>
                <a-button type="primary" style="margin-left: 8px;">查询</a-button>
              </span>
            </a-col>
          </a-row>
        </a-form>
      </div>
      <div class="table-operator">
        <a-button type="primary" icon="plus">新建</a-button>
      </div>
      <s-table
        ref="table"
        size="default"
        rowKey="key"
        :columns="columns"
        :data="loadData"
        showPagination="auto">
        <span slot="serial" slot-scope="text, record, index">
          
        </span>
        <span slot="year" slot-scope="text">
          <ellipsis :length="4" tooltip></ellipsis>
        </span>
        <span slot="installNumber" slot-scope="text">
          <ellipsis :length="4" tooltip></ellipsis>
        </span>
        <span slot="action">
          <template>
            <a>编辑</a>
            <a-divider type="vertical" />
            <a style="color: #f5222d;">删除</a>
          </template>
        </span>
      </s-table>
    </a-card>
    <a-card title="今年安装台数及进度" style="margin-top: 24px;" class="card">
      <div class="table-page-search-wrapper">
        <a-form layout="inline" :form="installNumbersForm">
          <a-row :gutter="48">
            <a-col :md="8" :sm="24">
              <a-form-item label="前期跟踪电梯台数">
                <a-input placeholder="" v-decorator="['previousTrack']" />
              </a-form-item>
            </a-col>
            <a-col :md="8" :sm="24">
              <a-form-item label="进行中电梯台数">
                <a-input placeholder="" v-decorator="['ongo']" />
              </a-form-item>
            </a-col>
            <a-col :md="8" :sm="24">
              <a-form-item label="已完成电梯台数">
                <a-input placeholder="" v-decorator="['hasBeenComplet']" />
              </a-form-item>
            </a-col>
            <a-col :md="24" :sm="24">
              <span class="table-page-search-submitButtons" style="float: right; overflow: hidden;">
                <a-button>取消</a-button>
                <a-button type="primary" style="margin-left: 8px;">确定</a-button>
              </span>
            </a-col>
          </a-row>
        </a-form>
      </div>
    </a-card>
    <a-card title="样板工程完成台数/优质项目数量" style="margin-top: 24px;" class="card">
      <div class="table-page-search-wrapper">
        <a-form layout="inline" :form="sampleProjectForm">
          <a-row :gutter="48">
            <a-col :md="8" :sm="24">
              <a-form-item label="样板工程完成台数">
                <a-input placeholder="" v-decorator="['modelProjectCompleteSets']" />
              </a-form-item>
            </a-col>
            <a-col :md="8" :sm="24">
              <a-form-item label="样板工程计划完成台数">
                <a-input placeholder="" v-decorator="['planToCompleteNumbers']" />
              </a-form-item>
            </a-col>
            <a-col :md="8" :sm="24">
              <a-form-item label="优质项目完成数量">
                <a-input placeholder="" v-decorator="['highQualityProjectCompletion']" />
              </a-form-item>
            </a-col>
            <a-col :md="8" :sm="24">
              <a-form-item label="优质项目计划完成数量">
                <a-input placeholder="" v-decorator="['highQualityProjectPlanToComplete']" />
              </a-form-item>
            </a-col>
            <a-col :md="16" :sm="24">
              <span class="table-page-search-submitButtons" style="float: right; overflow: hidden;">
                <a-button>取消</a-button>
                <a-button type="primary" style="margin-left: 8px;">确定</a-button>
              </span>
            </a-col>
          </a-row>
        </a-form>
      </div>
    </a-card>
  </div>
</template>

<script>
import { STable, Ellipsis } from '@/components'

export default {
  name: 'DynamicData',
  components: {
    STable,
    Ellipsis
  },
  data () {
    return {
      // 查询参数
      queryParam: {},
      // 表头
      columns: [
        {
          title: '#',
          scopedSlots: { customRender: 'serial' }
        },
        {
          title: '年份',
          dataIndex: 'year'
        },
        {
          title: '安装台数',
          dataIndex: 'installNumber'
        },
        {
          title: '操作',
          dataIndex: 'action',
          width: '150px',
          scopedSlots: { customRender: 'action' }
        }
      ],
      // 表格数据 必须为 Promise 对象
      loadData: (parameter) => {
        return new Promise((resolve) => {
          resolve({
            data: [],
            pageSize: 10,
            pageNo: 1,
            totalPage: 0,
            totalCount: 0
          })
        }).then((res) => {
          return res
        })
      },
      installNumbersForm: this.$form.createForm(this),
      sampleProjectForm: this.$form.createForm(this)
    }
  }
}
</script>

<style lang="less" scoped>
.card {
  .ant-form-item {
    flex-direction: column;
  }
  /deep/ .ant-form-item-label {
    text-align: left;
  }
  .table-page-search-submitButtons {
    margin-bottom: 0px;
    margin-top: 24px;
  }
}
</style>

添加路由

页面创建完成后,我们怎么在浏览器看到我们创建的页面呢,这时候我们就需要添加一个路由把它跟这个页面绑定起来,然后通过输入路由地址或者点击左侧菜单进入到我们创建的页面。

所有的路由和菜单都在src/config/目录下面的router.config.js文件进行统一的配置管理,它主要涉及一下三个功能(详细介绍可以看这里):

  • 路由管理
  • 菜单生成
  • 面包屑

router.config.js里在根路由下面新增安装部分子路由:

// eslint-disable-next-line
import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
import { bxAnaalyse } from '@/core/icons'

export const asyncRouterMap = [

  {
    path: '/',
    name: 'index',
    component: BasicLayout,
    meta: { title: '首页' },
    redirect: '/install/dynamic-data',
    children: [
      // 安装部分路由
      {
        path: 'install',
        name: 'Install',
        redirect: '/install/dynamic-data',
        component: RouteView,
        // 菜单配置
        meta: { title: '安装部分', keepAlive: false, icon: bxAnaalyse },
        children: [
          // 动态数据概览路由
          {
            path: 'dynamic-data',
            name: 'DynamicData',
            // 绑定页面
            component: () => import('@/views/install/DynamicData'),
            // 菜单配置
            meta: { title: '动态数据概览', keepAlive: false }
          }
        ]
      }
    ]
  },
  {
    path: '*', redirect: '/404', hidden: true
  }
]

/**
 * 基础路由
 * @type { *[] }
 */
export const constantRouterMap = [
  {
    path: '/user',
    component: UserLayout,
    redirect: '/user/login',
    hidden: true,
    children: [
      {
        path: 'login',
        name: 'login',
        component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login')
      },
      {
        path: 'register',
        name: 'register',
        component: () => import(/* webpackChunkName: "user" */ '@/views/user/Register')
      },
      {
        path: 'register-result',
        name: 'registerResult',
        component: () => import(/* webpackChunkName: "user" */ '@/views/user/RegisterResult')
      },
      {
        path: 'recover',
        name: 'recover',
        component: undefined
      }
    ]
  },

  {
    path: '/test',
    component: BlankLayout,
    redirect: '/test/home',
    children: [
      {
        path: 'home',
        name: 'TestHome',
        component: () => import('@/views/Home')
      }
    ]
  },

  {
    path: '/404',
    component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')
  }

]

现在我们运行起来看下效果:

这时候我们已经成功的创建了一个页面,并且让它在前端展示出来了。

Mock模拟数据

页面写完之后,发现后端接口还没写完,导致页面没有数据展示,表格空荡荡的我看着就很不爽,这时候我们就可以使用Mock生成大量的模拟数据来填充我们的页面。

Mock.js是一款模拟数据生成器,它可以模拟后端接口,生成随机数据,可模拟对数据的增删改查,用法也非常的简单,在已有接口文档的情况下,我们可以直接按照接口文档来开发,将相应的字段写好,在接口完成之后,只需要改变url地址即可。

Ant Design Pro中已经为我们继承好了Mock,在src/mock/services/目录下新建install.js文件,用于编写安装部分的模拟数据,写入以下一小坨的代码:

import Mock from 'mockjs2'
import { builder, getQueryParameters } from '../util'

// 历年安装电梯总台数表格模拟数据
const installElevatorAlwaysSetsList = (options) => {
    const totalCount = 5701 // 表格数据总数
    const result = [] // 表格数据
    const parameters = getQueryParameters(options) // 请求参数
    const pageSize = parseInt(parameters.pageSize) // 每页条数
    const pageNo = parseInt(parameters.pageNo) // 当前页码
    const totalPage = Math.ceil(totalCount / pageSize) // 总页数
    const key = (pageNo - 1) * pageSize
    const next = (pageNo >= totalPage ? totalCount % pageSize : pageSize) + 1
    for (let i = 1; i < next; i++) {
    const tmpKey = key + i
    result.push({
        id: tmpKey,
        key: tmpKey,
        year: Mock.mock('@date("yyyy")'), // 年份-生成随机年份
        installNumber: Mock.mock('@integer(1, 9999)') // 安装台数-生成1到9999之间随机数据
    })
    }

    return builder({
    pageSize: pageSize,
    pageNo: pageNo,
    totalCount: totalCount,
    totalPage: totalPage,
    data: result
    })
}

// 今年安装台数及进度模拟数据
const installNumbers = (options) => {
    return builder({
        previousTrack: Mock.mock('@integer(1, 9999)'), // 前期跟踪电梯台数
        ongo: Mock.mock('@integer(1, 9999)'), // 进行中电梯台数
        hasBeenComplet: Mock.mock('@integer(1, 9999)') // 已完成电梯台数
    })
}

// 样板工程完成台数/优质项目数量模拟数据
const sampleProject = (options) => {
    return builder({
        modelProjectCompleteSets: Mock.mock('@integer(1, 9999)'), // 样板工程完成台数
        planToCompleteNumbers: Mock.mock('@integer(1, 9999)'), // 样板工程计划完成台数
        highQualityProjectCompletion: Mock.mock('@integer(1, 9999)'), // 样板工程计划完成台数
        highQualityProjectPlanToComplete: Mock.mock('@integer(1, 9999)') // 优质项目计划完成数量
    })
}

// 模拟AJAX请求
Mock.mock(/\/installElevatorAlwaysSetsList/, 'get', installElevatorAlwaysSetsList)
Mock.mock(/\/installNumbers/, 'get', installNumbers)
Mock.mock(/\/sampleProject/, 'get', sampleProject)

上面我分别写了三个模拟数据的方法,然后在src/mock/index.js导入该文件:

import { isIE } from '@/utils/util'

// 判断环境不是 prod 或者 preview 是 true 时,加载 mock 服务
if (process.env.NODE_ENV !== 'production' || process.env.VUE_APP_PREVIEW === 'true') {
  if (isIE()) {
    console.error('[antd-pro] ERROR: `mockjs` NOT SUPPORT `IE` PLEASE DO NOT USE IN `production` ENV.')
  }
  // 使用同步加载依赖
  // 防止 vuex 中的 GetInfo 早于 mock 运行,导致无法 mock 请求返回结果
  console.log('[antd-pro] mock mounting')
  const Mock = require('mockjs2')
  require('./services/auth')
  require('./services/user')
  require('./services/manage')
  require('./services/other')
  require('./services/tagCloud')
  require('./services/article')
  require('./services/install') // 导入install.js

  Mock.setup({
    timeout: 800 // setter delay time
  })
  console.log('[antd-pro] mock mounted')
}

axios调用接口

我们可以使用axios来使用我们的Mock数据,这个在Ant Design Pro中也已经继承好了,但常用的请求方式我们还是要自己简单的封装一下,在src/api/目录下新建util.js,用于封装常用的GET、POST、PUT和DELETE请求:

import { axios } from '@/utils/request'

// post
export function postAction (url, parameter) {
  return axios({
    url: url,
    method: 'post',
    data: parameter
  })
}

// post method= {post | put}
export function httpAction (url, parameter, method) {
  return axios({
    url: url,
    method: method,
    data: parameter
  })
}

// put
export function putAction (url, parameter) {
  return axios({
    url: url,
    method: 'put',
    data: parameter
  })
}

// get
export function getAction (url, parameter) {
  return axios({
    url: url,
    method: 'get',
    params: parameter
  })
}

// delete
export function deleteAction (url, parameter) {
  return axios({
    url: url,
    method: 'delete',
    params: parameter
  })
}

然后在src/api/目录下新建api.js用于存放接口地址:

// eslint-disable-next-line
import { getAction, postAction, httpAction, putAction, deleteAction } from '@/api/utils'

const installElevatorAlwaysSetsList = (params) => getAction('installElevatorAlwaysSetsList', params) // 获取历年安装电梯总台数数据
const installNumbers = (params) => getAction('installNumbers', params) // 获取今年安装台数及进度数据
const sampleProject = (params) => getAction('sampleProject', params) // 获取样板工程完成台数/优质项目数量数据

export {
    installElevatorAlwaysSetsList,
    installNumbers,
    sampleProject
}

最后我们可以在DynamicData.vue页面调用这些接口了,在DynamicData.vue里的script标签内容做出如下更改:

<script>
import pick from 'lodash.pick'
import { STable, Ellipsis } from '@/components'
import { installElevatorAlwaysSetsList, installNumbers, sampleProject } from '@/api/api'

export default {
  name: 'DynamicData',
  components: {
    STable,
    Ellipsis
  },
  data () {
    return {
      // 查询参数
      queryParam: {},
      // 表头
      columns: [
        {
          title: '#',
          scopedSlots: { customRender: 'serial' }
        },
        {
          title: '年份',
          dataIndex: 'year'
        },
        {
          title: '安装台数',
          dataIndex: 'installNumber'
        },
        {
          title: '操作',
          dataIndex: 'action',
          width: '150px',
          scopedSlots: { customRender: 'action' }
        }
      ],
      // 表格数据 必须为 Promise 对象
      loadData: (parameter) => {
        return installElevatorAlwaysSetsList(Object.assign(parameter, this.queryParam)).then((res) => {
            return res.result
        })
      },
      installNumbersForm: this.$form.createForm(this),
      sampleProjectForm: this.$form.createForm(this)
    }
  },
  created () {
      this.initData()
  },
  methods: {
        initData () {
            // 获取今年安装台数及进度数据
            installNumbers().then((res) => {
                // 表单赋值
                this.installNumbersForm.setFieldsValue(pick(res.result, ['previousTrack', 'ongo', 'hasBeenComplet']))
            })
            // 获取样板工程完成台数/优质项目数量数据
            sampleProject().then((res) => {
                // 表单赋值
                this.sampleProjectForm.setFieldsValue(
                    pick(res.result, [
                        'modelProjectCompleteSets',
                        'planToCompleteNumbers',
                        'highQualityProjectCompletion',
                        'highQualityProjectPlanToComplete'
                    ])
                )
            })
        }
  }
}
</script>

最后我们再来看下页面效果:

写在最后

到这里,从创建页面到添加路由再到模拟数据获取数据这一整个的流程我们就走完了,当然上面那个页面并没有做完,还剩一些功能没有开发:条件搜索、新增、编辑和删除,还有表单提交这个功能,这些留到后面再讲吧~哈哈哈

上一篇:《Vue从零开始(2):使用Vue CLI快速生成项目脚手架》