浏览代码

feat(mobile-h5): add thin uni.request wrapper for API calls

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 月之前
父节点
当前提交
2bc7f353f0
共有 2 个文件被更改,包括 105 次插入 和 0 次删除
  1. 83 0
      mobile-h5/utils/request.js
  2. 22 0
      mobile-h5/utils/request.test.mjs

+ 83 - 0
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<string, string> }} options
+ * @returns {Promise<any>} 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 })
+}

+ 22 - 0
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 === '网络异常,请稍后重试'
+  )
+})