19. 前端页面开发

本章涉及的知识点如下:

  • 使用Vue.js开发一个实际项目;

  • 使用Vue.js与后端接口进行交互,并显示相关数据;

  • 切分一个项目的页面,主要是组件的划分和复用;-

  • 保证前后端数据的统一。

19.1. 1.项目前期准备

本节将会创建一个新的Vue.js项目,完成项目所需全部依赖库的安装,并且为Vue.js项目开发安装一些必备的库和模块,例如,符合风格的UI库,以及项目与后端数据接口进行交互的请求库。

19.1.1. 1.1 创建新项目

$ vue create app

安装多个依赖包,包括Babel、router、Vuex和ESLint

使用如下命令进入项目,并且启动该项目的开发环境。

$ cd app
$ npm run serve

编译完成后即可在本机查看,默认地址为http://localhost:8080/。

19.2. 2. 选择UI库

本例选择iView作为UI库。在最新版本中iView已经更名为view-design,使用如下命令安装:

$ npm install view-ui-plus --save

以上命令自动将iView的所有依赖包下载至本地,并且添加进package.json的依赖配置中。

依赖包除了手动安装外,还可以在vue-cli中安装。无论采用何种安装方式,对于UI库的使用来说没有任何区别。

安装完成的iView不能直接使用,需要在项目中引入,需要在Webpack中指定项目入口进行配置。如果使用vue-cli,则入口文件是main.js。在文件中修改代码如下:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ViewUIPlus from 'view-ui-plus'
import 'view-ui-plus/dist/styles/viewuiplus.css'

const app = createApp(App)

app.use(store)
  .use(router)
  .use(ViewUIPlus)
  .mount('#app')

如果要在项目中使用iView提供的UI组件,则需要修改项目自带的Home.vue文件。代码如下:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <Space wrap>
      <Button type="info">Info</Button>
      <Button type="success">Success</Button>
      <Button type="warning">Warning</Button>
      <Button type="error">Error</Button>
    </Space>
  </div>
</template>

这样就可以在项目的所有页面中使用iView提供的UI组件库了。这种方式采用全局引用,意味着所有iView中的组件无论是否使用均被引入,造成了资源浪费。

为了解决这个问题,iView提供了按需载入功能,借助插件babel-plugin-import实现。首先安装Babel,然后配置.babelrc(或vue-cli自动生成的babel.config.js),代码如下:

npm install babel-plugin-import --save-dev

// .babelrc or babel-loader
{
  "plugins": [
    [
      "import",
      {
        "libraryName": "view-ui-plus",
        "libraryDirectory": "src/components"
      },
      "view-ui-plus"
    ]
  ]
}

然后这样按需引入组件,就可以减小体积了:

// 按需导入
import { Button, Table } from 'view-ui-plus';
app.component('Button', Button);
app.component('Table', Table);

目前版本的iView,无论是否采用按需引用方式,都需要导入iView.css样式文件。

19.3. 3. 安装HTTP请求库

一个Vue.js项目,除了编辑页面的样式外,使用最多的一个功能就是对后端API的请求。本例使用Axios访问API。Axios是一个强大的HTTP库,用在浏览器或Node.js中。

使用Axios的好处是,开发者不必确定当前的应用环境。也就是说,在第8章中使用Express进行后端开发的场景中,使用Axios请求API也是可以的,并且两者的写法没有任何区别,由Axios自动判断。

不仅如此,Axios还支持Promise API,并且提供了自动转换JSON数据和XSRF防御的功能。使用如下命令安装Axios:

$ npm install axios - save

在请求中有时需要更改请求的头部,如增加Token,或者需要对请求进行统一处理,这就需要封装Axios。

在项目中新建utils文件夹,用来存放一些应用类的JavaScript文件,这里新建api.js文件用来封装Axios。

// get请求
api.get = async (url, params) => {
  return await apiAxios('GET', url, params)
}
// post请求
api.post = async (url, params) => {
  return await apiAxios('POST', url, params)
}

module.exports = api

在暴露给外部的两个方法对象中,调用apiAxios()方法制作统一的请求,该方法实例化了Axios进行请求,并且针对不同的请求方法添加参数,如果用户已经登录(会话存储为Token键值),其代码如下:

const axios = require('axios')
const baseUrl = 'http://localhost:3000/'
const api = {}

const apiAxios = async (method, url, params) => {
  // 项目既定fapp
  const headers = { fapp: 'book', 'Content-Type': 'application/json' }
  // 读取存储在sessionStorage中的token
  if (sessionStorage.getItem('token')) {
    headers.token = sessionStorage.getItem('token')
  }
  return await new Promise(resolve => {
    axios({
      // 如果缓存里有token则所有请求都包含其
      headers: headers,
      method: method,
      url: baseUrl + url,
      // 数据内容
      data:
                method === 'POST' ? params : null,
      params:
                method === 'GET' ? params : null
    }).then((res) => {
      console.log(res.data)
      resolve(res.data)
    }).catch(e => {
      console.log(e)
    })
  })
}

接下来在main.js文件中引入封装的API请求,并且将其挂载在Vue.js的全局对象中,这样可以在所有的场景中使用。修改后的main.js文件代码如下:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ViewUIPlus from 'view-ui-plus'
import 'view-ui-plus/dist/styles/viewuiplus.css'
import api from './utils/api'

const app = createApp(App)
app.config.globalProperties.$api = api

app.use(store)
  .use(router)
  .use(ViewUIPlus)
  .mount('#app')

需要注意的是,Axios在Vue.js中运行时采用类似于AJAX的方式请求服务器,如果根域名或端口不同,就会产生跨域问题,浏览器默认会阻止发送此类请求。

19.3.1. 3.1 跨域问题解决办法

此类问题无法避免,一般采用以下3种解决办法:

  • 设计反向代理,解决跨域问题;

  • 使用JSONP,允许用户传递一个callback参数给服务器端;

  • 在服务器端设置res的头部信息,允许所有请求或部分指定来源(确定的IP或者IP段)的请求。

本例选择第3种方案,修改server编写的服务器端代码,为其指定一个全局路由中间件,将所有的路由都设置为允许跨域。修改app.js文件代码如下:

var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var {checkAPP, checkUser, checkAdmin} = require('./util/middleware')

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
//增加管理员路由
var adminRouter = require('./routes/admin');

var app = express();

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

//设置允许跨域访问该服务.
//设置跨域访问
app.all('*', function(req, res, next){
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "*");
    next();
});

app.use('/', checkAPP, indexRouter);
app.use('/users', checkAPP, usersRouter);
app.use('/admin', [checkAPP, checkUser, checkAdmin], adminRouter);
module.exports = app;

这样跨域请求就不会产生错误了,也可以成功获取需要的数据。