diff --git a/common/js/diy.js b/common/js/diy.js index cab3742..dc1be9b 100644 --- a/common/js/diy.js +++ b/common/js/diy.js @@ -1,6 +1,7 @@ import WxMap from 'common/js/map-wx-jssdk.js'; import Config from '@/common/js/config.js'; import util from '@/common/js/util.js'; +import { classifyHttpError, httpErrorKey } from '@/common/js/http-error.js'; let systemInfo = util.getDeviceInfo(); @@ -326,30 +327,19 @@ export default { // 根据错误信息组织更详细的提示文案 // 说明:remote-only 模式下 $lang 可能返回空串,故统一用 `|| 中文兜底` 保证非空 _buildDiyErrorMsg(err) { - // 无错误信息 -> 通用网络异常 - if (!err) return this.$lang('diy.networkError'); - // 服务端明确返回的业务消息(字符串) - if (typeof err === 'string') return err; + const info = classifyHttpError(err); - const statusCode = err.statusCode; - const errMsg = err.errMsg || err.message || ''; - let reason; + // 服务端明确返回的业务消息(字符串):直接透传 + if (info.type === 'business') return info.raw; - if (statusCode) { - // HTTP 状态码类错误 - if (statusCode === 404) reason = this.$lang('diy.errNotFound'); - else if (statusCode >= 500) reason = this.$lang('diy.errServer'); - else if (statusCode === 401 || statusCode === 403) reason = this.$lang('diy.errAuth'); - else reason = this.$lang('diy.errRequest'); - reason += '(' + statusCode + ')'; - } else if (/timeout/i.test(errMsg)) { - reason = this.$lang('diy.errTimeout'); - } else if (/(connect|network|fail)/i.test(errMsg)) { - reason = this.$lang('diy.errNetwork'); - } else { - // 未知错误 -> 通用网络异常 - return this.$lang('diy.networkError'); - } + const key = diyErrorKey(info.type); + let reason = this.$lang(key); + + // HTTP 状态码类错误:把 statusCode 拼到 reason 后面 + if (info.statusCode) reason += '(' + info.statusCode + ')'; + + // empty / unknown 不走模板,直接返回通用网络异常 + if (info.type === 'empty' || info.type === 'unknown') return reason; const tpl = this.$lang('diy.loadFailTpl'); return tpl.replace('{reason}', reason); diff --git a/common/js/http-error.js b/common/js/http-error.js new file mode 100644 index 0000000..64506a2 --- /dev/null +++ b/common/js/http-error.js @@ -0,0 +1,101 @@ +/** + * 通用网络/接口错误分类器 + * + * 适用场景: + * - uniapp 各端统一错误分类(diy.js / 商品 / 订单 / 自定义页面等) + * - 任意传入 {statusCode, errMsg, message} 形式错误对象的位置 + * + * 设计要点: + * 1) 与 Vue / $lang 解耦:不调用任何 i18n 函数,只返回分类结果 + 原始错误字段 + * 调用方拿到 type 后,自行决定使用 $lang('http.timeout') / $lang('http.serverError') + * 或其它命名空间的文案,完全保持各模块原有的 key 命名习惯 + * 2) 通用网络/接口错误统一映射到 common.http.* 命名空间: + * - http.networkError: 通用网络异常(兜底) + * - http.timeout: 请求超时 + * - http.serverError: 服务器错误(5xx) + * - http.notFound: 接口不存在(404) + * - http.auth: 无访问权限(401/403) + * - http.unknown: 其他非 2xx + * 3) 兼容多种错误对象形态: + * - uniapp request: { statusCode, errMsg, data } + * - axios: { response: { status }, message } + * - fetch / 原生: { message, name } + * + * @param {Object|string} err 错误对象或后端业务消息字符串 + * @returns {{ type: string, statusCode?: number, errMsg?: string, raw?: any }} + * - type 取值: + * 'business' - 后端返回的业务消息字符串(已透传到 raw) + * 'empty' - 错误对象为空 + * 'notFound' - HTTP 404 + * 'server' - HTTP 5xx + * 'auth' - HTTP 401 / 403 + * 'request' - 其它非 2xx 状态码 + * 'timeout' - errMsg 含 timeout + * 'network' - errMsg 含 connect / network / fail + * 'unknown' - 无法分类 + * - statusCode: 若有则原样返回 + * - errMsg: 归一后的原始消息 + * - raw: 原始输入,方便调用方进一步处理 + */ +export function classifyHttpError(err) { + // 1) 无错误对象 + if (!err) { + return { type: 'empty', raw: err }; + } + + // 2) 后端直接返回的业务消息(字符串) + if (typeof err === 'string') { + return { type: 'business', raw: err }; + } + + // 3) 字段归一(兼容 uni / axios / 原生 Error) + const statusCode = err.statusCode + ?? err.response?.status + ?? err.status; + const errMsg = err.errMsg || err.message || ''; + + if (statusCode) { + if (statusCode === 404) return { type: 'notFound', statusCode, errMsg, raw: err }; + if (statusCode >= 500) return { type: 'server', statusCode, errMsg, raw: err }; + if (statusCode === 401 || statusCode === 403) return { type: 'auth', statusCode, errMsg, raw: err }; + return { type: 'request', statusCode, errMsg, raw: err }; + } + + if (/timeout/i.test(errMsg)) { + return { type: 'timeout', errMsg, raw: err }; + } + if (/(connect|network|fail)/i.test(errMsg)) { + return { type: 'network', errMsg, raw: err }; + } + + return { type: 'unknown', errMsg, raw: err }; +} + +/** + * 根据 classifyHttpError 的结果,映射到 common.http.* 命名空间的 i18n key + * + * 通用网络/接口错误统一走 http.*,调用方通过 $lang(key) 解析文案。 + * 原 diy.errTimeout 这类 key 已合并到 http.*,保持项目国际化更专业。 + * + * @param {string} type classifyHttpError 返回的 type + * @returns {string|null} 对应的 http.* key;type='business' 时返回 null(透传 raw) + */ +export function httpErrorKey(type) { + switch (type) { + case 'empty': return 'http.networkError'; + case 'business': return null; // 透传,不用 key + case 'notFound': return 'http.notFound'; + case 'server': return 'http.serverError'; + case 'auth': return 'http.auth'; + case 'request': return 'http.unknown'; + case 'timeout': return 'http.timeout'; + case 'network': return 'http.networkError'; + case 'unknown': + default: return 'http.networkError'; + } +} + +export default { + classifyHttpError, + httpErrorKey, +}; \ No newline at end of file diff --git a/lang/en-us/common.js b/lang/en-us/common.js index 12f4bb9..0624a64 100644 --- a/lang/en-us/common.js +++ b/lang/en-us/common.js @@ -54,21 +54,14 @@ export const lang = { serviceUnavailable: 'Navigation service temporarily unavailable', }, - // diy custom page + // diy custom page (diy-specific copy, generic HTTP errors reuse http.*) diy: { - networkError: 'Network error, please try again later', networkErrorTitle: 'Notice', retry: 'Retry', noDiyData: 'Custom page data not configured', locationNotAvailable: 'Location not available', - // detailed data-load failure messages + // detailed data-load failure template (diy-specific, {reason} is filled by generic http.*) loadFailTpl: 'Failed to load page data: {reason}, please try again later', - errNotFound: 'API not found', - errServer: 'Server error', - errAuth: 'No access permission', - errRequest: 'Request failed', - errTimeout: 'Request timeout', - errNetwork: 'Network connection failed', }, //customer service @@ -144,13 +137,17 @@ export const lang = { requestFailed: '{url} request failed. {err}', // network timeout timeout: 'Network request timeout, please try again later', - // network connection failed - networkError: 'Network connection failed, please check your network', + // network connection failed / generic network error (unified copy, reused by diy and other generic scenarios) + networkError: 'Network error, please try again later', // server error - serverError: 'Service temporarily unavailable, please try again later', + serverError: 'Server error, please try again later', + // API not found (HTTP 404) + notFound: 'API not found', + // no access permission (HTTP 401 / 403) + auth: 'No access permission', // request aborted aborted: 'Request cancelled', - // unknown error (errorType fallback, e.g. non-5xx HTTP status like 404) + // unknown error (errorType fallback, e.g. non-5xx/404/401/403 HTTP status) unknown: 'Request failed, please try again later', // unknown error unknownError: 'Request failed, please try again later', diff --git a/lang/zh-cn/common.js b/lang/zh-cn/common.js index 1540e31..c67b946 100644 --- a/lang/zh-cn/common.js +++ b/lang/zh-cn/common.js @@ -61,21 +61,14 @@ export const lang = { weChatWorkKefu: '企业微信客服', }, - // diy 自定义页面 + // diy 自定义页面(diy 专属文案,通用网络/接口错误复用 http.*) diy: { - networkError: '网络异常,请稍后重试', networkErrorTitle: '提示', retry: '重试', noDiyData: '未配置自定义页面数据', locationNotAvailable: '未获取到定位', - // 数据加载失败详细文案 + // 数据加载失败详细文案(diy 专属模板,内部 {reason} 由通用 http.* 填充) loadFailTpl: '页面数据加载失败:{reason},请稍后重试', - errNotFound: '接口不存在', - errServer: '服务器异常', - errAuth: '无访问权限', - errRequest: '请求失败', - errTimeout: '请求超时', - errNetwork: '网络连接失败', }, // 客服服务 @@ -144,13 +137,17 @@ export const lang = { requestFailed: '{url}请求失败. {err}', // 网络超时 timeout: '网络请求超时,请稍后重试', - // 网络连接失败 - networkError: '网络连接失败,请检查网络设置', + // 网络连接失败 / 通用网络异常(统一文案,供 diy 等通用场景复用) + networkError: '网络异常,请稍后重试', // 服务器错误 - serverError: '服务暂时不可用,请稍后重试', + serverError: '服务器异常,请稍后重试', + // 接口不存在(HTTP 404) + notFound: '接口不存在', + // 无访问权限(HTTP 401 / 403) + auth: '无访问权限', // 请求被中断 aborted: '请求已取消', - // 未知错误(errorType 分类兜底,如非 5xx 的 HTTP 状态码 404 等) + // 未知错误(errorType 分类兜底,如非 5xx/404/401/403 的 HTTP 状态码) unknown: '请求失败,请稍后重试', // 未知错误 unknownError: '请求失败,请稍后重试',