first commit

This commit is contained in:
Mrx
2026-05-16 15:47:07 +08:00
commit 03de10f800
146 changed files with 33663 additions and 0 deletions

35
src/api/auth.js Normal file
View 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
View 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 useApiFetchh5 / 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,31 @@
import { http } from './http'
/** 微信小程序:使用 `uni.login` 得到的 `code` 换 tokentyc-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
}