vxe-table ajax 自定义错误_ajax模式

a5bb916d335458b2a72c09049bbde211.png

记录一个项目里用到的小东西,关于怎么写 ajax 的。

我要实现这个效果: 不需要处理任何服务端的错误, 不需要理会服务端的数据结构, 调用一个函数, 即可拿到我想要的结果。

import loadUser from '@/io/user'
async function task() {
    let user = await loadUser(userCode)
}

项目里,我们一般使用 axios 这个库来做基础的 ajax 支持

基于 axios 的 api,已经可以实现一个很好的效果了, 但是前端还是不得不处理两个很烦人的东西

  1. 后端给的数据是被包裹的
  2. 后端会报错

比如一个常见的服务端响应结构:

{
    "type": 0, //0 表示成功,-1表示失败
    "data": null, // 成功了的话,这里放数据,失败了的话这里什么都没有
    "message": "" // 失败了的话这里放失败的具体信息,成功了的话这里什么都没有
}

先不提应该尽量使用 rest api 来处理这些异常的问题了, 比如我见过一份规范文档里头,因为 tomcat 的某个版本的 patch 方法有安全问题,然后全局把 patch/delete 这些方法都禁用掉了

根据公司不同,规范文档不同,这里的数据结构也不尽相同

但是按照传统的做法,这里的代码还是很冗长,繁琐

loadUser()
    .then(response => {
        if (response.type == 0) {
            let user = response.data
        } else {
            showMessage(response.message)
        }
    })
    .catch(error => {
        if (error.response.statusCode > 500) {
            showError('服务端错误')
        } else {
            //.....
        }
    })

即使我们用上异步函数, 这里的代码还是会很繁琐

所以我的做法是充分利用 axios.interceptors

let baseUrl = '/web-context' // axios 有一个 baseURL 选项,不这么用是个人习惯,为了更灵活,不是所有的url都需要这个
axios.interceptors.request.use(config => {
    if (!config.url.startsWith('http')) {
        config.url = `${baseUrl}${config.url}`
    }
    return config
})
function handleResult(data) {
    // 这一段是根据具体项目,可以随意变动的,在组织项目的时候,可以让这一部分被传递进来
    if (data.type == 0) {
        return data.data
    } else {
        throw new Error(data.message)
    }
}
function showMessage(httpCode, config, res) {
    // 这里也是项目技术决定的,如何显示异常信息,这个函数的具体实现,也可以被传递进来
    if (!config.opt.showMessage) {
        return
    }
    if (httpCode == 200) {
        // 服务端主动抛出来的错误
        showInfo(res.message)
    } else if (httpCode > 500) {
        showError('服务端内部错误')
    } else if (httpCode > 400) {
        showError('客户端错误')
    } else {
        showError('未知错误')
    }
}
function encrypt(res) {
    if (process.env.NODE_ENV == 'development') {
        return res
    }
    // 同样的,可以根据具体项目随意的变动
    return res.code // 由于这里的值可能会被直接打印在控制台上,所以可以处理一下,不将敏感信息直接打印出来
}
axios.intercepors.response.use(
    res => {
        if (res.status == 200) {
            // 为了简单起见, 这里会在构建请求方法的时候,保证这里的opt选项,不需要多做一次判断
            // 可以由接口定义处来处理这里的设置,完全不处理响应体,可以保证兼容项目里所有的接口响应格式
            //   防止部分接口响应格式不统一的问题
            if (res.config.opt.handleResult) {
                try{
                    let result = await handleResult(res.data)
                    return Promise.resolve(result)
                }catch(e){
                    showMessage(200,res.config, res.data)
                    return Promise.reject(e)
                }
            }
            return res.data
        }
        showMessage(200, res.config, res.data)
        return Promise.reject(new Error(encrypt(res.data)))
    },
    error => {
        let { response } = error
        if (response.status >= 500) {
            message(response.data, 'error')
        } else if (response.status >= 400) {
            message(response.data, 'warning')
        }
        showMessage(response.status, error.config, response.data)
        return Promise.reject(new Error(encrypt(response.data)))
    }
)

基于以上代码, 即可做到基本的请求处理, 可以实现自动处理异常,自动处理提示信息, 对于业务来说, 代码已经简化成了这样

async function task() {
    try {
        let user = await axios({
            url: '/load-user',
            method: 'get',
            param: { code: '1234' },
            opt: {
                handleResult: true,
                showMessage: true
            }
        })
    } catch (e) {
        // 所有在 interceptor.response 中 reject 的分支,都会进入这里, 如果默认效果即可,则不需要写这一层catch
        // 如果这里没有catch, 则会被算作未捕获的异常,直接会打印到控制台
    }
}

但是这东西还是一大坨, 每次调用都写一坨, 很别扭,很难看, 而且,通信接口这东西,一旦定下来了,那么 url,method 这些,都是固定的,每次调用都一毛一样, 那么再进行一次重构

async function loadUser(code, opt = {}) {
    return axios({
        url: '/load-user',
        method: 'get',
        param: {
            code
        },
        opt: {
            //是否需要调用自定义的结果处理函数,一般来说,一个项目的响应格式是统一的,都应该调用,但是不排除有部分接口是不统一的,允许自行配置,不处理这个响应体
            handleResult: true,
            // 是否调用通用的提示信息函数,一般来说,都是需要的,但是不排除部分接口的提示信息会由接口调用处来处理,这里允许调用处将这个配置关闭
            //   从技术上来说,是可以判断编程人员是否catch到异常的,但是写了catch不一定是要做信息提示,可能只是为了恢复程序流程,保证能够继续执行,所以这里的提示是否开启,还是需要一个单独的配置来做
            showMessage: true,
            ...opt
        }
    })
}

再来, 如果我们写了好多接口,就会发现,这里绝大部分的东西还是一致的, 比如如果都是 get 请求,那么基本只有 url 会有区别,其他内容完全一致

再重构一次

function genGetInterface(url) {
    return function(code, opt = {}) {
        return axios({
            url,
            method: 'get',
            param: { code },
            opt: {
                handleResult: true,
                showMessage: true,
                ...opt
            }
        })
    }
}
const loadUser = genGetInterface('/load-user')
const loadRole = genGetInterface('/load-role')

对于一些不太统一的接口

const loadSth = async data => {
    return axios({
        url: '/load-sth',
        method: 'post',
        data,
        opt: {
            handleResult: false,
            showMessage: false
        }
    })
}

以上,即可实现一个效果还不从的 ajax 实现模式 -- 前提是项目里绝大部分的接口很规范统一,如果接口都千奇百怪的,就不要考虑怎么处理这些了,先去把后端教训一顿吧

最后,如果项目遵守 rest 规范

那么可以得到这么一份生成器

function genGet(url) {
    return (code, opt) =>
        axios({
            url: `${url}/${code}`,
            method: 'GET',
            opt
        })
}

function genGetList(url) {
    return (pageSize, pageNo, searchParam, opt) =>
        axios({
            url,
            method: 'GET',
            params: {
                pageSize,
                pageNo,
                searchParam
            },
            opt
        })
}
function genCreate(url) {
    return (model, opt) =>
        axios({
            url,
            method: 'POST',
            data: model,
            opt
        })
}

function genUpdate(url) {
    return (code, model, opt) =>
        axios({
            url: `${url}/${code}`,
            method: 'PATCH',
            data: model,
            opt
        })
}

function genDelete(url) {
    return (codes, opt) =>
        axios({
            url,
            method: 'DELETE',
            data: codes,
            opt
        })
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 `vxe-table` 中,可以通过 `renderHeader` 和 `renderCell` 两个属性对表格的列进行自定义排版。 `renderHeader` 属性用于自定义表头的排版,它的值是一个函数,该函数返回一个 VNode,用于渲染表头的内容。例如,如果要在表头中添加一个图标,可以这样设置 `renderHeader`: ```js { title: '列名', field: 'fieldName', renderHeader: function (h, { column }) { return h('div', [ h('i', { class: 'icon icon-sort' }), h('span', column.title) ]) } } ``` 在这个例子中,`renderHeader` 函数返回一个 `div` 元素,其中包含一个图标和列名。在 `h` 函数中,第一个参数表示要创建的元素的标签名,第二个参数表示元素的属性和事件监听器,第三个参数表示元素的子节点,它可以是一个字符串或一个 VNode。 `renderCell` 属性用于自定义单元格的排版,它的值是一个函数,该函数返回一个 VNode,用于渲染单元格的内容。例如,如果要将某一列的数据格式化为货币形式,可以这样设置 `renderCell`: ```js { title: '金额', field: 'amount', renderCell: function (h, { row }) { return h('span', { class: 'currency' }, '$' + row.amount.toFixed(2)) } } ``` 在这个例子中,`renderCell` 函数返回一个 `span` 元素,其中包含格式化后的金额。在 `h` 函数中,第一个参数表示要创建的元素的标签名,第二个参数表示元素的属性和事件监听器,第三个参数表示元素的子节点。在这个例子中,我们使用了 `toFixed` 方法将金额格式化为两位小数,并在前面添加了美元符号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值