first commit
This commit is contained in:
35
src/api/auth.js
Normal file
35
src/api/auth.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { http } from './http'
|
||||
|
||||
/**
|
||||
* 发送短信验证码。
|
||||
* 注意:与 `/user/mobileCodeLogin` 校验的 Redis 键一致,必须使用 actionType `login`
|
||||
*(后端 mobileCodeLogin 固定校验 `login:` 前缀;新用户同一接口会自动注册)。
|
||||
*/
|
||||
export async function postAuthSendSms({ mobile, captchaVerifyParam = '' }) {
|
||||
const res = await http.post('/auth/sendSms', {
|
||||
mobile,
|
||||
actionType: 'login',
|
||||
captchaVerifyParam,
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 绑定手机号场景,Redis 键为 bindMobile:(须与后端 BindMobile 校验一致) */
|
||||
export async function postAuthSendSmsBindMobile({ mobile, captchaVerifyParam = '' }) {
|
||||
const res = await http.post('/auth/sendSms', {
|
||||
mobile,
|
||||
actionType: 'bindMobile',
|
||||
captchaVerifyParam,
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 查询页发短信(Redis 键为 `query:`,与 H5 `InquireForm` actionType `query` 一致) */
|
||||
export async function postAuthSendSmsQuery({ mobile, captchaVerifyParam = '' }) {
|
||||
const res = await http.post('/auth/sendSms', {
|
||||
mobile,
|
||||
actionType: 'query',
|
||||
captchaVerifyParam,
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
163
src/api/http.js
Normal file
163
src/api/http.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import un from '@uni-helper/uni-network'
|
||||
|
||||
/** 未单独配置登录页时,401 回到「我的」 */
|
||||
const AUTH_FALLBACK_PAGE = '/pages/mine'
|
||||
|
||||
const TOKEN_KEY = 'token'
|
||||
const SILENT_TOAST_CODES = new Set([200002, 200003, 200004, 100009])
|
||||
|
||||
let loadingCount = 0
|
||||
|
||||
function showRequestLoading() {
|
||||
if (loadingCount++ === 0) {
|
||||
uni.showLoading({ title: '加载中...', mask: true })
|
||||
}
|
||||
}
|
||||
|
||||
function hideRequestLoading() {
|
||||
if (--loadingCount <= 0) {
|
||||
loadingCount = 0
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
/** H5 与其它端分支由 uni 条件编译裁剪,源码中并存会触发 no-unreachable */
|
||||
function resolveBaseUrl() {
|
||||
const fromEnv = import.meta.env.VITE_API_BASE_URL
|
||||
if (fromEnv)
|
||||
return fromEnv.replace(/\/$/, '')
|
||||
/* eslint-disable no-unreachable */
|
||||
// #ifdef H5
|
||||
return '/api/v1'
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return 'https://www.tianyuancha.cn/api/v1'
|
||||
// #endif
|
||||
/* eslint-enable no-unreachable */
|
||||
}
|
||||
|
||||
/** 对齐 tyc-webview-v2 useApiFetch:h5 / wxh5,其它端单独标识 */
|
||||
function getXPlatform() {
|
||||
let platform = 'h5'
|
||||
// #ifdef H5
|
||||
const ua = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : ''
|
||||
if (/micromessenger/.test(ua))
|
||||
platform = 'wxh5'
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
// 须与 tyc-server-v2 model.PlatformWxMini("wxmini")一致,勿用 mp-weixin,否则 JWT 生成会失败
|
||||
platform = 'wxmini'
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
platform = 'mp-alipay'
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
platform = 'app'
|
||||
// #endif
|
||||
return platform
|
||||
}
|
||||
|
||||
function readToken() {
|
||||
try {
|
||||
const t = uni.getStorageSync(TOKEN_KEY)
|
||||
return typeof t === 'string' ? t : ''
|
||||
}
|
||||
catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAuthStorage() {
|
||||
const keys = ['token', 'refreshAfter', 'accessExpire', 'userInfo', 'agentInfo']
|
||||
for (const k of keys) {
|
||||
try {
|
||||
uni.removeStorageSync(k)
|
||||
}
|
||||
catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleHttpUnauthorized() {
|
||||
clearAuthStorage()
|
||||
uni.showToast({ title: '登录已失效', icon: 'none' })
|
||||
uni.reLaunch({ url: AUTH_FALLBACK_PAGE })
|
||||
}
|
||||
|
||||
function handleForceLogout() {
|
||||
clearAuthStorage()
|
||||
uni.reLaunch({ url: '/pages/index' })
|
||||
}
|
||||
|
||||
function toastBizIfNeeded(body) {
|
||||
if (body.code === 200)
|
||||
return
|
||||
if (SILENT_TOAST_CODES.has(body.code))
|
||||
return
|
||||
const text = body.msg || '请求失败'
|
||||
uni.showToast({ title: text, icon: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 tyc-webview-v2 `useApiFetch` 对齐的实例:
|
||||
* - baseUrl `/api/v1`(H5 默认,可配 VITE_API_BASE_URL)
|
||||
* - Header:`Authorization`、`X-Platform`
|
||||
* - Query:`t` 时间戳防缓存
|
||||
* - 业务码:401、100009、非 200 的 Toast 策略与 H5 一致(无 Pinia 时不重置 store)
|
||||
*/
|
||||
export const http = un.create({
|
||||
baseUrl: resolveBaseUrl(),
|
||||
timeout: 60_000,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
http.interceptors.request.use((config) => {
|
||||
if (!config.skipLoading)
|
||||
showRequestLoading()
|
||||
const token = readToken()
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
'X-Platform': getXPlatform(),
|
||||
...(token ? { Authorization: token } : {}),
|
||||
}
|
||||
config.params = {
|
||||
...(config.params || {}),
|
||||
t: Date.now(),
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
http.interceptors.response.use(
|
||||
(response) => {
|
||||
if (!response.config?.skipLoading)
|
||||
hideRequestLoading()
|
||||
const status = response.status ?? 0
|
||||
if (status === 401) {
|
||||
handleHttpUnauthorized()
|
||||
return response
|
||||
}
|
||||
const body = response.data
|
||||
if (body && typeof body === 'object' && 'code' in body) {
|
||||
if (body.code === 100009)
|
||||
handleForceLogout()
|
||||
else if (!response.config?.skipBizToast)
|
||||
toastBizIfNeeded(body)
|
||||
}
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
const errCfg = error?.config || error?.response?.config
|
||||
if (!errCfg?.skipLoading)
|
||||
hideRequestLoading()
|
||||
const status = error?.response?.status
|
||||
if (status === 401)
|
||||
handleHttpUnauthorized()
|
||||
else if (!errCfg?.skipBizToast && typeof error?.message === 'string' && error.message)
|
||||
uni.showToast({ title: error.message, icon: 'none' })
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
8
src/api/index.js
Normal file
8
src/api/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './auth'
|
||||
export { clearAuthStorage, http } from './http'
|
||||
export * from './pay'
|
||||
export * from './product'
|
||||
export * from './query'
|
||||
export * from './toolbox'
|
||||
export * from './upload'
|
||||
export * from './user'
|
||||
7
src/api/pay.js
Normal file
7
src/api/pay.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { http } from './http'
|
||||
|
||||
/** 发起支付(与 H5 `Payment.vue` 一致) */
|
||||
export async function postPayPayment(body, requestConfig) {
|
||||
const res = await http.post('/pay/payment', body, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
8
src/api/product.js
Normal file
8
src/api/product.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { http } from './http'
|
||||
|
||||
/** 与 H5 `Inquire.vue` 一致:GET /api/v1/product/en/:product_en */
|
||||
export async function getProductByEn(productEn) {
|
||||
const enc = encodeURIComponent(productEn)
|
||||
const res = await http.get(`/product/en/${enc}`)
|
||||
return res.data
|
||||
}
|
||||
37
src/api/query.js
Normal file
37
src/api/query.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { http } from './http'
|
||||
|
||||
/** 示例报告(与 H5 `Example.vue` 一致:GET /api/v1/query/example?feature=) */
|
||||
export async function getQueryExample(feature) {
|
||||
const enc = encodeURIComponent(feature)
|
||||
const res = await http.get(`/query/example?feature=${enc}`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 按订单号查报告详情(需 JWT,与 H5 同源 Query 结构) */
|
||||
export async function getQueryDetailByOrderNo(orderNo, requestConfig) {
|
||||
const enc = encodeURIComponent(orderNo)
|
||||
const res = await http.get(`/query/orderNo/${enc}`, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 按订单 ID 查报告详情(与 GET /query/orderId/:id 一致) */
|
||||
export async function getQueryDetailByOrderId(orderId, requestConfig) {
|
||||
const enc = encodeURIComponent(String(orderId))
|
||||
const res = await http.get(`/query/orderId/${enc}`, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 当前用户历史查询列表(GET /query/list) */
|
||||
export async function getQueryList(params = {}, requestConfig) {
|
||||
const page = params.page != null ? Number(params.page) : 1
|
||||
const pageSize = params.pageSize != null ? Number(params.pageSize) : 20
|
||||
const res = await http.get(`/query/list?page=${page}&page_size=${pageSize}`, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 创建查询临时单(与 H5 `InquireForm` 一致:POST /api/v1/query/service/:product) */
|
||||
export async function postQueryService(productEn, body, requestConfig) {
|
||||
const enc = encodeURIComponent(productEn)
|
||||
const res = await http.post(`/query/service/${enc}`, body, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
17
src/api/toolbox.js
Normal file
17
src/api/toolbox.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { http } from './http'
|
||||
|
||||
/**
|
||||
* 工具箱统一接口
|
||||
* @param {string} toolKey - 工具标识(对应 toolboxRegistry 中的 key)
|
||||
* @param {Record<string, any>} params - 工具参数
|
||||
* @returns {Promise<{code: number, msg: string, data: {tool_key: string, result: Record<string, any>}}>}
|
||||
*/
|
||||
export async function postToolboxQuery(toolKey, params = {}) {
|
||||
const res = await http.post('/toolbox/query', {
|
||||
tool_key: toolKey,
|
||||
params,
|
||||
}, {
|
||||
skipBizToast: true,
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
7
src/api/upload.js
Normal file
7
src/api/upload.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { http } from './http'
|
||||
|
||||
/** 行驶证等图片上传,返回可访问 URL(与 H5 `InquireForm` `/upload/image` 一致) */
|
||||
export async function postUploadImage(imageBase64, requestConfig) {
|
||||
const res = await http.post('/upload/image', { image_base64: imageBase64 }, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
31
src/api/user.js
Normal file
31
src/api/user.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { http } from './http'
|
||||
|
||||
/** 微信小程序:使用 `uni.login` 得到的 `code` 换 token(tyc-server-v2 WxMiniAuth) */
|
||||
export async function postUserWxMiniAuth(body, requestConfig) {
|
||||
const res = await http.post('/user/wxMiniAuth', body, requestConfig)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 已登录用户绑定手机号(需 JWT,见 tyc-server-v2 BindMobile) */
|
||||
export async function postUserBindMobile(body) {
|
||||
const res = await http.post('/user/bindMobile', body)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 手机号 + 短信验证码登录(新用户自动注册,见 tyc-server-v2 MobileCodeLoginLogic) */
|
||||
export async function postUserMobileCodeLogin(body) {
|
||||
const res = await http.post('/user/mobileCodeLogin', body)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 刷新 accessToken(对齐 tyc-webview-v2 App.vue `refreshToken`) */
|
||||
export async function postUserGetToken() {
|
||||
const res = await http.post('/user/getToken')
|
||||
return res.data
|
||||
}
|
||||
|
||||
/** 用户信息(对齐 tyc-webview-v2 userStore.fetchUserInfo) */
|
||||
export async function getUserDetail() {
|
||||
const res = await http.get('/user/detail')
|
||||
return res.data
|
||||
}
|
||||
Reference in New Issue
Block a user