From 8abc7edbd5a9588f298e055c34b0f26ec98a76c2 Mon Sep 17 00:00:00 2001 From: ljh666 <13216698987@163.com> Date: Thu, 10 Oct 2019 21:45:25 +0800 Subject: [PATCH] axios --- .env | 3 + .env.production | 3 + mock/article.js | 115 ++++++++++++++++++++++++++++++++++++++ mock/index.js | 68 ++++++++++++++++++++++ mock/mock-server.js | 68 ++++++++++++++++++++++ mock/remote-search.js | 51 +++++++++++++++++ mock/user.js | 85 ++++++++++++++++++++++++++++ package.json | 7 ++- postcss.config.js | 2 +- src/main.js | 5 ++ src/store/modules/user.js | 0 src/utils/index.js | 19 +++++++ src/utils/request.js | 92 ++++++++++++++++++++++++++++++ src/views/Home.vue | 18 +++++- vue.config.js | 27 ++++++--- yarn.lock | 89 ++++++++++++++++++++++++++--- 16 files changed, 633 insertions(+), 19 deletions(-) create mode 100644 .env create mode 100644 .env.production create mode 100644 mock/article.js create mode 100644 mock/index.js create mode 100644 mock/mock-server.js create mode 100644 mock/remote-search.js create mode 100644 mock/user.js create mode 100644 src/store/modules/user.js create mode 100644 src/utils/index.js create mode 100644 src/utils/request.js diff --git a/.env b/.env new file mode 100644 index 0000000..eb1a913 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +NODE_ENV = 'development' +BASE_URL = '/' +VUE_APP_BASE_API = '' \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..475b412 --- /dev/null +++ b/.env.production @@ -0,0 +1,3 @@ +NODE_ENV = 'production' +BASE_URL = './' +VUE_APP_BASE_API = 'https://easy-mock.com/' \ No newline at end of file diff --git a/mock/article.js b/mock/article.js new file mode 100644 index 0000000..079a61a --- /dev/null +++ b/mock/article.js @@ -0,0 +1,115 @@ +import Mock from 'mockjs' + +const List = [] +const count = 100 + +const baseContent = '
I am testing data, I am testing data.
' +const imageUri = 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3' + +for (let i = 0; i < count; i++) { + List.push(Mock.mock({ + id: '@increment', + timestamp: +Mock.Random.date('T'), + author: '@first', + reviewer: '@first', + title: '@title(5, 10)', + content_short: 'mock data', + content: baseContent, + forecast: '@float(0, 100, 2, 2)', + importance: '@integer(1, 3)', + 'type|1': ['CN', 'US', 'JP', 'EU'], + 'status|1': ['published', 'draft', 'deleted'], + display_time: '@datetime', + comment_disabled: true, + pageviews: '@integer(300, 5000)', + imageUri, + platforms: ['a-platform'] + })) +} + +export default [ + { + url: '/article/list', + type: 'get', + response: config => { + const { importance, type, title, page = 1, limit = 20, sort } = config.query + + let mockList = List.filter(item => { + if (importance && item.importance !== +importance) return false + if (type && item.type !== type) return false + if (title && item.title.indexOf(title) < 0) return false + return true + }) + + if (sort === '-id') { + mockList = mockList.reverse() + } + + const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) + + return { + code: 200, + data: { + total: mockList.length, + items: pageList + } + } + } + }, + + { + url: '/article/detail', + type: 'get', + response: config => { + const { id } = config.query + for (const article of List) { + if (article.id === +id) { + return { + code: 200, + data: article + } + } + } + } + }, + + { + url: '/article/pv', + type: 'get', + response: _ => { + return { + code: 200, + data: { + pvData: [ + { key: 'PC', pv: 1024 }, + { key: 'mobile', pv: 1024 }, + { key: 'ios', pv: 1024 }, + { key: 'android', pv: 1024 } + ] + } + } + } + }, + + { + url: '/article/create', + type: 'post', + response: _ => { + return { + code: 200, + data: 'success' + } + } + }, + + { + url: '/article/update', + type: 'post', + response: _ => { + return { + code: 200, + data: 'success' + } + } + } +] diff --git a/mock/index.js b/mock/index.js new file mode 100644 index 0000000..8cee444 --- /dev/null +++ b/mock/index.js @@ -0,0 +1,68 @@ +import Mock from 'mockjs' +import { param2Obj } from '../src/utils' + +import user from './user' +import article from './article' +import search from './remote-search' + +const mocks = [ + ...user, + ...article, + ...search +] + +// 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). +export 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) + } + } + + for (const i of mocks) { + Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) + } +} + +// for mock server +const responseFake = (url, type, respond) => { + return { + url: new RegExp(`/mock${url}`), + type: type || 'get', + response (req, res) { + res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) + } + } +} + +export default mocks.map(route => { + return responseFake(route.url, route.type, route.response) +}) diff --git a/mock/mock-server.js b/mock/mock-server.js new file mode 100644 index 0000000..ccb5902 --- /dev/null +++ b/mock/mock-server.js @@ -0,0 +1,68 @@ +const chokidar = require('chokidar') +const bodyParser = require('body-parser') +const chalk = require('chalk') +const path = require('path') + +const mockDir = path.join(process.cwd(), 'mock') + +function registerRoutes (app) { + let mockLastIndex + const { default: mocks } = require('./index.js') + for (const mock of mocks) { + app[mock.type](mock.url, mock.response) + mockLastIndex = app._router.stack.length + } + const mockRoutesLength = Object.keys(mocks).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)] + } + }) +} + +module.exports = app => { + // es6 polyfill + require('@babel/register') + + // 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)) + } + } + }) +} diff --git a/mock/remote-search.js b/mock/remote-search.js new file mode 100644 index 0000000..bb33c2f --- /dev/null +++ b/mock/remote-search.js @@ -0,0 +1,51 @@ +import Mock from 'mockjs' + +const NameList = [] +const count = 100 + +for (let i = 0; i < count; i++) { + NameList.push(Mock.mock({ + name: '@first' + })) +} +NameList.push({ name: 'mock-Pan' }) + +export default [ + // username search + { + url: '/search/user', + type: 'get', + response: config => { + const { name } = config.query + const mockNameList = NameList.filter(item => { + const lowerCaseName = item.name.toLowerCase() + return !(name && lowerCaseName.indexOf(name.toLowerCase()) < 0) + }) + return { + code: 20000, + data: { items: mockNameList } + } + } + }, + + // transaction list + { + url: '/transaction/list', + type: 'get', + response: _ => { + return { + code: 20000, + data: { + total: 20, + 'items|20': [{ + order_no: '@guid()', + timestamp: +Mock.Random.date('T'), + username: '@name()', + price: '@float(1000, 15000, 0, 2)', + 'status|1': ['success', 'pending'] + }] + } + } + } + } +] diff --git a/mock/user.js b/mock/user.js new file mode 100644 index 0000000..ebc3a7b --- /dev/null +++ b/mock/user.js @@ -0,0 +1,85 @@ + +const tokens = { + admin: { + token: 'admin-token' + }, + editor: { + token: 'editor-token' + } +} + +const users = { + 'admin-token': { + roles: ['admin'], + introduction: 'I am a super administrator', + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', + name: 'Super Admin' + }, + 'editor-token': { + roles: ['editor'], + introduction: 'I am an editor', + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', + name: 'Normal Editor' + } +} + +export default [ + // user login + { + url: '/user/login', + type: 'post', + response: config => { + const { username } = config.body + const token = tokens[username] + + // mock error + if (!token) { + return { + code: 60204, + message: 'Account and password are incorrect.' + } + } + + return { + code: 200, + data: token + } + } + }, + + // get user info + { + // eslint-disable-next-line + url: '/user/info\.*', + type: 'get', + response: config => { + const { token } = config.query + const info = users[token] + + // mock error + if (!info) { + return { + code: 508, + message: 'Login failed, unable to get user details.' + } + } + + return { + code: 200, + data: info + } + } + }, + + // user logout + { + url: '/user/logout', + type: 'post', + response: _ => { + return { + code: 200, + data: 'success' + } + } + } +] diff --git a/package.json b/package.json index dfba527..2e80fb6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "axios": "^0.19.0", - "core-js": "^2.6.5", + "core-js": "2.6.5", "lib-flexible": "^0.3.2", "vant": "^2.2.5", "vue": "^2.6.10", @@ -17,6 +17,7 @@ "vuex": "^3.0.1" }, "devDependencies": { + "@babel/register": "^7.6.2", "@vue/cli-plugin-babel": "^3.11.0", "@vue/cli-plugin-eslint": "^3.11.0", "@vue/cli-service": "^3.11.0", @@ -24,8 +25,12 @@ "amfe-flexible": "^2.2.1", "babel-eslint": "^10.0.1", "babel-plugin-import": "^1.12.2", + "body-parser": "^1.19.0", + "chalk": "^2.4.2", + "chokidar": "^3.2.1", "eslint": "^5.16.0", "eslint-plugin-vue": "^5.0.0", + "mockjs": "^1.0.1-beta3", "postcss-pxtorem": "^4.0.1", "sass": "^1.18.0", "sass-loader": "^7.1.0", diff --git a/postcss.config.js b/postcss.config.js index f81ae0e..da66063 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -4,7 +4,7 @@ module.exports = { overrideBrowserslist: ['Android >= 4.0', 'iOS >= 7'] }, 'postcss-pxtorem': { - rootValue: 75, + rootValue: 37.5, propList: ['*'] } } diff --git a/src/main.js b/src/main.js index c638f55..73fab27 100644 --- a/src/main.js +++ b/src/main.js @@ -3,8 +3,13 @@ import 'lib-flexible' import App from './App.vue' import router from './router' import store from './store' +import { mockXHR } from '../mock' import '@/style/common.scss' +if (process.env.NODE_ENV === 'development') { + mockXHR() +} + Vue.config.productionTip = false new Vue({ diff --git a/src/store/modules/user.js b/src/store/modules/user.js new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..42dd9b8 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,19 @@ +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj (url) { + const search = url.split('?')[1] + if (!search) { + return {} + } + return JSON.parse( + '{"' + + decodeURIComponent(search) + .replace(/"/g, '\\"') + .replace(/&/g, '","') + .replace(/=/g, '":"') + .replace(/\+/g, ' ') + + '"}' + ) +} diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..15c34db --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,92 @@ +import axios from 'axios' +import { Toast } from 'vant' +import store from '@/store' +// import { getToken } from '@/utils/auth' + +// create an axios instance +const service = axios.create({ + // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url + // withCredentials: true, // send cookies when cross-domain requests + timeout: 5000 // request timeout +}) + +// request interceptor +service.interceptors.request.use( + config => { + // do something before request is sent + Toast.loading({ + mask: true, + message: '加载中...' + }) + 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 + console.log(error, 'err') // for debug + 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 => { + // Toast.fail({ + // message: '怎么回事怎么回事', + // duration: 1.5 * 1000 + // }) + const res = response.data + console.log(res, 'res.data') + + // if the custom code is not 20000, it is judged as an error. + if (res.code !== 200) { + // Toast({ + // message: res.message || 'Error', + // type: 'error', + // duration: 5 * 1000 + // }) + + // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; + if (res.code === 50008 || res.code === 50012 || res.code === 50014) { + // to re-login + Toast.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() + }) + }) + } + return Promise.reject(new Error(res.message || 'Error')) + } else { + return res + } + }, + error => { + console.log('err' + error) // for debug + Toast.fail({ + message: error.message, + duration: 1.5 * 1000 + }) + return Promise.reject(error) + } +) + +export default service diff --git a/src/views/Home.vue b/src/views/Home.vue index 8ca19fe..f1ace95 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -13,7 +13,7 @@