/** * 统一 API 请求封装,完全对齐 ai_uniapp_v2/utils/request.js * * 请求路径走 /api 前缀 → server/api/[...].js 服务端代理 * /api/user/info → 代理到 https://api.jiefuku.com/user/info * * 自动注入(对齐 V2): * Header: { token, timestr, nonce } * Body: { token, uid, mac, base_timestamp, client:1, source, client_ios, version, version_code } * * 后端响应规范: * { code: 0, data: {...}, list: [...], msg: '...' } // 成功 * { code: 1000, data: null, msg: '...' } // 业务异常 * { code: 1009, data: null, msg: '...' } // 登录失效 */ import md5 from '~/utils/md5' export function useApi() { const tokenCookie = useCookie('token', { path: '/' }) const macCookie = useCookie('mac', { maxAge: 365 * 24 * 3600, path: '/' }) const openIdCookie = useCookie('open_id', { path: '/' }) const sourceCookie = useCookie('source', { path: '/' }) // mac 缺失时自动生成 if (import.meta.client && !macCookie.value) { macCookie.value = generateUUID() } function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0 const v = c === 'x' ? r : (r & 0x3) | 0x8 return v.toString(16) }) } /** * 生成 nonce + timestr(对齐 V2 getNonce) */ function getNonce() { const timeStr = Date.now().toString() const str = md5(timeStr) const nonce = str.substring(4, 14) return { timeStr, nonce } } /** * 发起请求 */ async function request(path, options = {}) { const { method = 'GET', params, body } = options // 走 /api 代理,对齐 V2 H5 端的 BASE_URL = '/api' const url = '/api' + path const timestamp = Math.floor(Date.now() / 1000) const { timeStr, nonce } = getNonce() // Header 签名(对齐 V2 header 注入) const reqHeaders = { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', timestr: timeStr, nonce, } const token = tokenCookie.value if (token) reqHeaders.token = token // Body 公共参数(对齐 V2 data 注入) const commonParams = { token: token || '', uid: openIdCookie.value || '', mac: macCookie.value || '', base_timestamp: timestamp, client: 1, source: sourceCookie.value || '', client_ios: 0, version: '1.0.0', version_code: 1, } const fetchOptions = { method, headers: reqHeaders, onResponseError({ response }) { console.error('[useApi] HTTP Error', response.status, url) }, } if (method === 'GET') { // GET: 参数拼接到 URL const allParams = { ...commonParams, ...(params || {}) } // 去除空字符串 Object.keys(allParams).forEach((k) => { if (allParams[k] === '') delete allParams[k] }) const queryStr = new URLSearchParams(allParams).toString() fetchOptions.query = allParams // GET 请求通过 server proxy 时,参数在 query 里 const finalUrl = url + (queryStr ? '?' + queryStr : '') const result = await $fetch(finalUrl, fetchOptions) return handleResult(result) } else { // POST: body 为 x-www-form-urlencoded const postBody = { ...commonParams, ...(body || {}) } // 去除空字符串 Object.keys(postBody).forEach((k) => { if (postBody[k] === '') delete postBody[k] }) fetchOptions.body = new URLSearchParams(postBody).toString() const result = await $fetch(url, fetchOptions) return handleResult(result) } } /** * 统一处理响应 */ function handleResult(result) { if (result.code === 1009) { // 登录失效 → 触发登录弹窗 if (import.meta.client) { const { logout } = useUser() logout() const showLogin = useState('showLogin', () => false) showLogin.value = true } const err = new Error(result.msg || '登录已失效,请重新登录') err.code = 1009 throw err } if (result.code !== 0) { const err = new Error(result.msg || '请求失败') err.code = result.code || 1000 throw err } return result } return { /** GET 请求 */ get: (path, params) => request(path, { method: 'GET', params }), /** POST 请求 (x-www-form-urlencoded) */ post: (path, body) => request(path, { method: 'POST', body }), /** 通用请求 */ request, } }