diff --git a/mobile-h5/utils/request.js b/mobile-h5/utils/request.js new file mode 100644 index 00000000..86b039bc --- /dev/null +++ b/mobile-h5/utils/request.js @@ -0,0 +1,83 @@ +import { API_BASE_URL, REQUEST_TIMEOUT_MS } from './config.js' + +/** + * 解析后端统一响应。成功返回 data;失败抛 Error(message 为业务文案)。 + * @param {{ code?: number, message?: string, data?: any }} body + * @param {number} [httpStatus] + */ +export function unwrapResponse(body, httpStatus) { + if (body == null || typeof body !== 'object') { + const err = new Error('网络异常,请稍后重试') + err.code = 'BAD_RESPONSE' + throw err + } + const code = body.code + if (code === 200) { + return body.data + } + const msg = + (typeof body.message === 'string' && body.message.trim()) || + '请求失败' + const err = new Error(msg) + err.code = code + err.httpStatus = httpStatus + throw err +} + +function joinUrl(path) { + const base = (API_BASE_URL || '').replace(/\/$/, '') + const p = path.startsWith('/') ? path : `/${path}` + return `${base}${p}` +} + +/** + * @param {{ url: string, method?: string, data?: any, header?: Record }} options + * @returns {Promise} data 字段 + */ +export function request(options) { + const method = (options.method || 'GET').toUpperCase() + const url = joinUrl(options.url) + const header = Object.assign( + { 'Content-Type': 'application/json' }, + options.header || {} + ) + + return new Promise((resolve, reject) => { + uni.request({ + url, + method, + data: options.data, + header, + timeout: REQUEST_TIMEOUT_MS, + success(res) { + try { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(unwrapResponse(res.data, res.statusCode)) + return + } + // HTTP 非 2xx:若 body 仍是业务包,优先展示 message + try { + resolve(unwrapResponse(res.data, res.statusCode)) + } catch (bizErr) { + reject(bizErr) + } + } catch (e) { + reject(e) + } + }, + fail() { + const err = new Error('网络异常,请稍后重试') + err.code = 'NETWORK' + reject(err) + } + }) + }) +} + +export function get(url, header) { + return request({ url, method: 'GET', header }) +} + +export function post(url, data, header) { + return request({ url, method: 'POST', data, header }) +} diff --git a/mobile-h5/utils/request.test.mjs b/mobile-h5/utils/request.test.mjs new file mode 100644 index 00000000..813b6ade --- /dev/null +++ b/mobile-h5/utils/request.test.mjs @@ -0,0 +1,22 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { unwrapResponse } from './request.js' + +test('unwrapResponse returns data when code is 200', () => { + const data = unwrapResponse({ code: 200, message: 'success', data: { token: 't' } }) + assert.deepEqual(data, { token: 't' }) +}) + +test('unwrapResponse throws with backend message when code is not 200', () => { + assert.throws( + () => unwrapResponse({ code: 401, message: '用户名或密码错误', data: null }), + (e) => e instanceof Error && e.message === '用户名或密码错误' && e.code === 401 + ) +}) + +test('unwrapResponse throws network message on null body', () => { + assert.throws( + () => unwrapResponse(null), + (e) => e instanceof Error && e.message === '网络异常,请稍后重试' + ) +})