前言:官網(wǎng)描述
1.前后端分離
2.不需要修改既有代碼质欲,就可以攔截 Ajax 請(qǐng)求绑嘹,返回模擬的響應(yīng)數(shù)據(jù)。
3.數(shù)據(jù)類型豐富
4.通過(guò)隨機(jī)數(shù)據(jù)年碘,模擬各種場(chǎng)景澈歉。
其實(shí)就是在后端接口開(kāi)發(fā)完成之前,我們前端可以用已有的接口文檔屿衅,在真實(shí)的請(qǐng)求上攔截ajax埃难,根據(jù)mockjs的mock數(shù)據(jù)的規(guī)則模擬真實(shí)接口返回的數(shù)據(jù),并將模擬數(shù)據(jù)返回參與相應(yīng)的數(shù)據(jù)交互處理涤久,這樣真正實(shí)現(xiàn)了前后臺(tái)的分離開(kāi)發(fā)涡尘。
與以往模擬的假數(shù)據(jù)不同,mockjs可以帶給我們的是:在后臺(tái)接口未開(kāi)發(fā)完成之前模擬數(shù)據(jù)并返回响迂,完成前臺(tái)的交互考抄;在后臺(tái)數(shù)據(jù)完成之后,你所做的只是去掉mockjs:停止攔截真實(shí)的ajax蔗彤,僅此而已川梅。
接下來(lái)就一步一步實(shí)現(xiàn)這個(gè)過(guò)程:
- 引入mockjs依賴
npm install mockjs --save-dev
-
建一個(gè)mock文件夾統(tǒng)一管理mock數(shù)據(jù)
image.png
3.在mock文件夾下建一個(gè)index.js(寫(xiě)關(guān)鍵代碼,攔截到我們前端發(fā)出的請(qǐng)求)
const Mock = require('mockjs')
// 解析地址欄參數(shù)的函數(shù)
const { param2Obj } = require('./utils')
// 導(dǎo)入模擬數(shù)據(jù)
const user = require('./user')
const role = require('./role')
const list = require('./list')
const mocks = [
...user,
...role,
...list
]
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
function mockXHR() {
// mock patch
// https://github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function() {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
this.proxy_send(...arguments)
}
function XHR2ExpressReqWrap(respond) {
return function(options) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
// https://expressjs.com/en/4x/api.html#req
result = respond({
method: type,
body: JSON.parse(body),
query: param2Obj(url)
})
} else {
result = respond
}
return Mock.mock(result)
}
}
// 批量注冊(cè)路由事件
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
module.exports = {
mocks,
mockXHR
}
- mock文件夾下新建utile.js文件
/**
* @param {string} url
* @returns {Object}
*/
function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
module.exports = {
param2Obj,
deepClone
}
- main.js文件中引入
// main.js 開(kāi)啟mock 服務(wù)
import { mockXHR } from '../mock'
if (process.env.NODE_ENV === 'development') {
mockXHR()
}
6.下面封裝有兩種封裝使用方式
一然遏、
(1)mock文件夾中新增mock-server.js文件封裝請(qǐng)求
下載安裝 chokidar挑势、body-parser、chalk啦鸣、path
chokidar(監(jiān)聽(tīng)文件變化):
npm install --save-dev chokidar
body-parser(配合post 通過(guò)req.body獲取前端的請(qǐng)求數(shù)據(jù)):
npm install --save-dev express ejs body-parser
chalk(命令行顏色的插件):
npm install --save-dev chalk
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const Mock = require('mockjs')
// path模塊是node.js中處理路徑的核心模塊潮饱。可以很方便的處理關(guān)于文件路徑的問(wèn)題诫给。
// join() 將多個(gè)參數(shù)值合并成一個(gè)路徑
const mockDir = path.join(process.cwd(), 'mock')
function registerRoutes(app) {
let mockLastIndex
const { mocks } = require('./index.js')
const mocksForServer = mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocksForServer).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength
}
}
function unregisterRoutes() {
Object.keys(require.cache).forEach(i => {
if (i.includes(mockDir)) {
delete require.cache[require.resolve(i)]
}
})
}
// for mock server
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
type: type || 'get',
response(req, res) {
console.log('request invoke:' + req.path)
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
module.exports = app => {
// parse app.body
// https://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
const mockRoutes = registerRoutes(app)
var mockRoutesLength = mockRoutes.mockRoutesLength
var mockStartIndex = mockRoutes.mockStartIndex
// watch files, hot reload mock server
chokidar.watch(mockDir, {
ignored: /mock-server/,
ignoreInitial: true
}).on('all', (event, path) => {
if (event === 'change' || event === 'add') {
try {
// remove mock routes stack
app._router.stack.splice(mockStartIndex, mockRoutesLength)
// clear routes cache
unregisterRoutes()
const mockRoutes = registerRoutes(app)
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
} catch (error) {
console.log(chalk.redBright(error))
}
}
})
}
(2)mockjs文件夾下新建幾個(gè)模擬數(shù)據(jù)文件(list.js)
// import Mock from 'mockjs'
const Mock = require('mockjs')
const List = []
const count = 50
for (let i = 0; i < count; i++) {
List.push(Mock.mock({
id: '@increment',
'date': '@date("yyyy-MM-dd")',
'name': '@cname',
'province': '@province',
'city': '@city',
'address': '@county(true)',
'postcode': '@zip'
}))
}
module.exports = [
{
url: '/vue-element-admin/commonList/getList',
type: 'get',
response: config => {
var { name, page, limit } = config.query
var mocklist = List.filter(item => {
if (name && item.name.indexOf(name) < 0) return false
return true
})
page = Number(page)
limit = Number(limit)
var newDataList = mocklist.slice((page - 1) * limit, page * limit)
return {
code: 20000,
data: {
page: page,
limit: limit,
total: List.length,
rows: newDataList
}
}
}
}
]
(3) 使用
getList(){
axios.get('/vue-element-admin/commonList/getList',{
params:{
page:1,
limit: 20
}
}).then(function(res){
this.goodsList = res.data.rows;
}).catch(function (error) {
console.log(error);
});
}
二香拉、
(1)封裝請(qǐng)求方法
import axios from "axios";
import { Loading, Message } from "element-ui";
import store from "@/store";
// import { getToken } from "@/utils/auth";
// create an axios instance
const service = axios.create({
// baseURL: "",
timeout: 10000, // request timeout
// headers: {
// "Content-Type": "multipart/form-data",
// },
});
let apiCallNo = 0;
let loadingInstance;
// request interceptor
// TODO 待優(yōu)化
service.interceptors.request.use(
(config) => {
if (config.data) {
const { hideLoading, ...rest } = config.data;
if (!hideLoading) {
apiCallNo += 1;
if (apiCallNo === 1) {
loadingInstance = Loading.service();
}
}
if (Object.keys(rest).length !== 0) {
config.data = rest;
} else if (typeof hideLoading === "boolean") {
config.data = null;
}
} else {
apiCallNo += 1;
if (apiCallNo === 1) {
loadingInstance = Loading.service();
}
}
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
// config.headers["X-Token"] = getToken();
}
return config;
},
(error) => {
// do something with request error
return Promise.reject(error);
}
);
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
(response) => {
apiCallNo -= 1;
if (apiCallNo === 0) {
loadingInstance.close();
}
const res = response.data;
// 導(dǎo)出二進(jìn)制流數(shù)據(jù)
if (res.type) {
return res;
}
// 普通請(qǐng)求
if (res.status !== 200) {
Message({
message: res.message || "Error",
type: "error",
duration: 5 * 1000,
});
return Promise.reject(new Error(res.message || "Error"));
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// // to re-login
// MessageBox.confirm(
// "You have been logged out, you can cancel to stay on this page, or log in again",
// "Confirm logout",
// {
// confirmButtonText: "Re-Login",
// cancelButtonText: "Cancel",
// type: "warning",
// }
// ).then(() => {
// store.dispatch("user/resetToken").then(() => {
// location.reload();
// });
// });
// }
} else {
return res.data;
}
},
(error) => {
console.log(error.response);
apiCallNo -= 1;
if (apiCallNo === 0) {
loadingInstance.close();
}
Message({
message: error.response?.data.message ?? "網(wǎng)絡(luò)異常,請(qǐng)重試", // TODO 是否要改成統(tǒng)一的提示中狂?
type: "error",
duration: 5 * 1000,
});
return Promise.reject(error);
}
);
export default service;
(2)新建一個(gè)文件專門(mén)封裝api
import request from "@/utils/request"; // 引入request方法
// 使用mock模擬后端數(shù)據(jù)
export function fetchResultTrend(params) {
return request({
url: "/mock/credit-evaluate-statistics/result/trend",
params,
});
}
(3)在vue文件中調(diào)用接口
async closerRateChange() {
const res = await fetchResultTrend();
console.log(res)
}
注:
npm / cnpm 下載時(shí)代表意義
node :自帶的包管理器
install :下載
--save :在package.json中存儲(chǔ)為線上需要的插件
--save-dev : 在package.json中存儲(chǔ)為線下需要的插件
express :基于node.js 平臺(tái)凫碌,快速、開(kāi)放胃榕、極簡(jiǎn)的文本開(kāi)發(fā)框架
ejs :通過(guò)數(shù)據(jù)和模板盛险,可以生成HTML標(biāo)記文本,可以同時(shí)運(yùn)行在客戶端和服務(wù)器端勋又,0 學(xué)習(xí)成本
body-parser :配合post 通過(guò)req.body獲取前端的請(qǐng)求數(shù)據(jù)
node .\app.js : 開(kāi)啟服務(wù)器苦掘,鏈接端口為listen設(shè)置的值
注意 :只要更改app.js ,都需要重新開(kāi)啟服務(wù)器