1 Star 0 Fork 0

轻语/vue-aixos

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
README.md 11.83 KB
一键复制 编辑 原始数据 按行查看 历史
轻语 提交于 3年前 . doc(readme)

行动笔记:

将内容联系实际思考或是创作完成的作品放在这里

import { merge } from '@/utils/index'
import axios from 'axios';
import QS from 'qs';
import { getTokenAUTH } from '@/utils/auth';
import { ElLoading, ElMessage } from 'element-plus';

const IS_USE_GLOBAL_MOCK = true // 是否全部用mock数据  true是  false否
const MOCK_SERVER_IP = 'http://localhost:8888/api'
// baseURL会给每个接口都加上对应前缀,而项目实际场景中,
// 存在一个前端工程,对应多个服务的场景。需要通过不用的前缀代理到不同的服务,故使用自定义prefixUrl
const DEFAULT_PREFIX_URL = process.env.NODE_ENV == 'development' ? 'http://dev.123dailu.com' : 'http://pro.123dailu.com'
const DEFAULT_AXIOS_CONFIG = {
  withCredentials: true, // 允许把cookie传递到后台
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json; charset=utf-8'
  },
  timeout: 10000,
}

const contentTypes = {
  json: 'application/json; charset=utf-8',
  urlencoded: 'application/x-www-form-urlencoded; charset=utf-8',
  multipart: 'multipart/form-data',
}

const LoadingInstance = {
  _target: null, // 保存Loading实例
  _count: 0
};
const pendingRequestMap = new Map();
const fetch = (url, {
  method = 'get',
  params = {}, // 参数,根据method封装参数形式
  axiosConfig = {}, // 自定义http配置 如responseType、headers
  prefixUrl = '',  // 自定义url前缀
  repeat_request = false, // 允许重复请求
  paramsType = 'json', 
  loading = true,
  loadingOptions = {  // elementUI loading配置
    text: '动静有时,大音希声'
  }
}) => {
  const axiosConfigIn = merge(
    DEFAULT_AXIOS_CONFIG,
    axiosConfig
  )
  const instance = axios.create();
  instance.interceptors.request.use(config => {
    config.headers.tokenId = getTokenAUTH()
    switch (method) {
      case 'get':
        config.params = params
        // 如果要在get请求设置content-Type
        if (axiosConfig?.headers?.contentType) { // 未设置requestData的时候源码会删掉Content-Type的设置,其这么做的原因在于是Get请求本身是不需要Content-Type的。
          config.data = true 
        }
        break;
      case 'post':
        config.headers['Content-Type'] = axiosConfig?.headers?.contentType || contentTypes[paramsType]
        // http://axios-js.com/zh-cn/docs/#%E4%BD%BF%E7%94%A8-application-x-www-form-urlencoded-format
        config.data = paramsType === 'urlencoded' ? QS.stringify(params) : params 
        break;
      default:
        break;
    }
    if (!repeat_request) {
      removePending(config); // 重复请求相关
      addPending(config);
    }
    if (loading) {
      LoadingInstance._count++;
      if(LoadingInstance._count === 1) {
        LoadingInstance._target = ElLoading.service(loadingOptions);
      }
    }
    // 移除起始部分 / 所有请求url走相对路径
    config.url = config.url.replace(/^\//, '')
    return config
  })

  instance.interceptors.response.use(
    res => {
      httpErrorStatusHandle(res)
      !repeat_request && removePending(res.config);
      loading && closeLoading(loading); // 关闭loading
      return Promise.resolve(res);
    },
    error => {
      httpErrorStatusHandle(error)
      error.config && !repeat_request && removePending(error.config);
      loading && closeLoading(loading); // 关闭loading
      return Promise.reject(error); // 错误继续返回给到具体页面
    }
  );
  return instance({
    url: IS_USE_GLOBAL_MOCK ? `${MOCK_SERVER_IP}/${url}` : (`${prefixUrl}` ? `${prefixUrl}/${url}` : `${DEFAULT_PREFIX_URL}/${url}`) ,
    method,
    ...axiosConfigIn
  })
}

/**
 * 处理异常
 * @param {*} error 
 */
const httpErrorStatusHandle = error => {
  let message
  if (error?.response) {
    const msgByCode = new Map([
      [302, '接口重定向'],
      [400, '参数不正确!'],
      [401, '您未登录,或者登录已经超时,请先登录!'], // TODO跳转登录页
      [403, '您没有权限操作!'],
      // [404, `请求地址出错: ${error?.response?.config?.url}`], // 在正确域名下
      [408, '请求超时!'],
      [409, '系统已存在相同数据!'],
      [500, '服务器内部错误!'],
      [501, '服务未实现!'],
      [502, '网关错误!'],
      [503, '服务不可用!'],
      [504, '服务暂时无法访问,请稍后再试!'],
      [505, 'HTTP版本不受支持!']
    ])
    message = msgByCode.get(error?.response?.status) || '异常问题,请联系管理员!'
  }
  if(axios.isCancel(error)) return console.error('请求的重复请求:' + error.message);
  if (error?.message?.includes('timeout')) message = '网络请求超时!';
  if (error?.message?.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!';
  message && 
  ElMessage({
    type: 'error',
    message
  })
}
/**
 * 关闭Loading层实例
 * @param {*} _options 
 */
const closeLoading = (loading) => {
  if(loading && LoadingInstance._count > 0) LoadingInstance._count--;
  if(LoadingInstance._count === 0) {
    LoadingInstance._target.close();
    LoadingInstance._target = null;
  }
}
/**
 * 生成每个请求唯一的键
 * @param {*} config 
 * @returns string
 */
const getPendingKey = (config) => {
  let { url, method, params, data } = config;
  data = typeof data === 'object' ? JSON.stringify(data) : '' + data
  return [url, method, JSON.stringify(params), data].join('&');
}
/**
 * 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
 * @param {*} config 
 */
const addPending = (config) => {
  const pendingKey = getPendingKey(config);
  console.log(pendingKey, 'pendingKey')
  config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
    if (!pendingRequestMap.has(pendingKey)) {
      pendingRequestMap.set(pendingKey, cancel);
    }
  });
}
/**
 * 删除重复的请求
 * @param {*} config 
 */
const removePending = (config) => {
  const pendingKey = getPendingKey(config);
  if (pendingRequestMap.has(pendingKey)) {
     const cancelToken = pendingRequestMap.get(pendingKey);
     cancelToken(pendingKey);
     pendingRequestMap.delete(pendingKey);
  }
}


export const get = (url, arg) => fetch(url, { ...arg, method: 'get' })
export const post = (url, arg) => fetch(url, { ...arg, method: 'post' })

附录

地址:vue-axios

cd vue-axios/service   // 开启本地服务器
nodemon app.js

cd vue-axios  
npm i & npm run dev

=====================================================================

构思笔记:

阅读后的所思所想,或是创作时的思维点滴在这里展现

  • prefixUrl
  • 支持get请求添加content-type
  • 支持loading配置
  • 支持异常状态提示
  • 支持取消重复请求
  • 支持post请求
  • Content-Type: application/json

    * Content-Type: application/x-www-form-urlencoded
    * Content-Type: multipart/form-data
    

取消请求

场景:频繁触发。 其实取消后的请求还是有可能会到达了后端,只是前端浏览器不处理而已。 优化手段可以在源头阶段比如防抖节流之类。 故axios重复请求取消作为兜底手段。 思路:* 地址、参数、方法一致则视为统一请求。将首次请求其维护在map中,第二次请求时先在map中判断是否任务已在队列中。 取消底层借助axios的cancelToken(底层还是xml的abort)。 new axios.CancelToken()给每个请求带上一个专属的CancelToken,之后会接收到一个cancel() 取消方法,用于后续的取消动作,所以我们需要对应的存储好这个方法。

Loading

  • 同一时间内发起多个请求,我们只需要展示一个Loading层即可,不需要产生多个造成重复展示。
  • 同一时间内发起多个请求展示的Loading层以最后一个请求响应而关闭销毁。
  • 支持element原生配置

状态码解释

window.navigator.onLine 来判断是否断网了。 timeout判断请求超时 ​

===================================================================== ​

封存笔记:

将收集到的资料,或是需要归档的记录放到封存层

  • get参数为params: {params: {}}, post为data。
  • 当content-type为application/x-www-form-urlencoded时,需要对参数进行序列化qs.stringify()或借用new URLSearchParams();
  • transformRequest 就是允许在向服务器发送前,修改请求数据,但只能用在 'PUT','POST' 和 'PATCH' 这几个请求方法,且后面数组中的函数必须返回一个字符串,或 ArrayBuffer,
  • Content-Type(内容类型):一般是指网页中存在的 Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些 PHP 网页点击的结果却是下载一个文件或一张图片的原因。告诉客户端实际返回的内容的内容类型。
  • get请求设置content-type时,需要为data赋值。 源码中会检索,若为false,则移除content-type。
  • 当使用export default导出时,import不能用解构赋值,原因在于 import 语句中的"解构赋值"并不是解构赋值,而是 named imports,语法上和解构赋值很像,但还是有所差别。
// 如下写法babel 6之前允许,babel 6之后就不能了。
// a.js
import { foo, bar } from "./b"
// b.js
export default {
  foo: "foo",
  bar: "bar"
}


export default {   // 当用webpack构建后会变成类似如下
  host: 'localhost',
  port: 80
}

module.exports.default = {
  host: 'localhost',
  port: 80
}
因此只能改成
// 方案1
import b from './b'
let { foo, bar } = b

// 方案2
// a.js
import { foo, bar } from "./b"
// b.js
let foo =  "foo"
let bar = "bar"
export { foo, bar }

// 方案3
// a.js
import { foo, bar } from "./b"
// b.js
export let foo =  "foo"
export let bar = "bar"
  • node进程守护5种方法
  1. forever
npm install forever -g   #安装
forever start app.js     #启动应用
forever stop app.js      #关闭应用
forever restart app.js   #重启应用
forever stopall          #关闭所有应用
forever restartall       #重启所有应用
forever list             #显示所有运行的应用
2. [PM2](http://pm2.keymetrics.io/)
npm install pm2 -g #安装
pm2 start app.js #启动
pm2 list    #查看所有运行中的应用
pm2 stop    #关闭
pm2 restart #重启
pm2 delete  #删除
3. [StrongLoop-PM](http://strong-pm.io)

4. [Supervisor](https://github.com/Supervisor/supervisor)
5. nohup
6. nodemon(随时监听文件的变更,自动重启服务)

vue3

  • vue.prototype.$http方式替换为globalProperties
import { createApp } from 'vue'
import App from './App.vue'
import {
  get,
  post,
} from '@/api/fetch'

const app = createApp(App)
app.config.globalProperties.$http = {
  get,
  post,
}

app.mount('#app')
// app.vue
const { appContext : { config: { globalProperties } } } = getCurrentInstance()
getCurrentInstance.$http.post()
  • vite配置alias和proxy
import {
  defineConfig
 } from 'vite'
 import path from "path";
 import vue from '@vitejs/plugin-vue'

 export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src")
    },
  },
   plugins: [vue()],
   server: {
     proxy: {
      '/api': 'http://localhost:8888/', //代理网址
      '/api2': 'http://localhost:8888/'//代理网址
    }
  }
 })
 
  • require.context替换为import.meta.globEager
const files = import.meta.globEager('./main/*.js')
const modules = {}
for (const item in files) { 
  if (Object.prototype.hasOwnProperty.call(files, item)) {
    const key = (item.replace(/(\.\/|\.js)/g, '')).split('/').slice(-1)[0]
    modules[key] = files[item].default 
  }
}
console.log(modules, 'modules')

export default modules

Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/yunbooks/vue-aixos.git
git@gitee.com:yunbooks/vue-aixos.git
yunbooks
vue-aixos
vue-aixos
develop

搜索帮助