Files
shanxia2.0/miniprogram/utils/bxRequest.js
T
2026-09-17 09:10:10 +08:00

244 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 报销/备用金模块 HTTP(bx 信封:code === 0)
* 对齐 bx-mobile/src/api/bxClient.ts
*/
import { TOKEN_KEY, REFRESH_TOKEN_KEY, USER_INFO_KEY, resolveApiBase } from '@/config/api'
const BX_COLLECTION_ROOT = /^\/(reimburses|advances|payments|workflows|expense-items)$/
function normalizeBxUrl(url) {
if (!url || url.endsWith('/')) return url
return BX_COLLECTION_ROOT.test(url) ? `${url}/` : url
}
function buildQuery(params) {
if (!params || typeof params !== 'object') return ''
const parts = []
Object.keys(params).forEach((key) => {
const val = params[key]
if (val === undefined || val === null || val === '') return
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(val))}`)
})
return parts.length ? `?${parts.join('&')}` : ''
}
function parseDetailMessage(data) {
if (!data || typeof data !== 'object') return ''
if (typeof data.msg === 'string' && data.msg) return data.msg
const detail = data.detail
if (typeof detail === 'string') return detail
if (Array.isArray(detail)) {
return detail
.map((x) => (x && x.msg) || '')
.filter(Boolean)
.join(';')
}
return data.message || ''
}
async function onUnauthorized() {
uni.removeStorageSync(TOKEN_KEY)
uni.removeStorageSync(REFRESH_TOKEN_KEY)
uni.removeStorageSync(USER_INFO_KEY)
try {
const { useUserStore } = await import('@/stores/user')
useUserStore().clearLoginInfo()
} catch (e) {
// ignore
}
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/login/login' })
}, 800)
}
/**
* @param {{ url: string, method?: string, data?: any, params?: any, showError?: boolean }} options
* @returns {Promise<{ code: number, data: any, msg?: string }>}
*/
export function bxRequest(options = {}) {
const method = String(options.method || 'GET').toUpperCase()
const showError = options.showError !== false
const token = uni.getStorageSync(TOKEN_KEY) || ''
let path = normalizeBxUrl(options.url || '')
const query = buildQuery(options.params)
const rawUrl = `${path}${query}`
const url = rawUrl.startsWith('http')
? rawUrl
: `${resolveApiBase()}${rawUrl.startsWith('/') ? rawUrl : `/${rawUrl}`}`
return new Promise((resolve, reject) => {
uni.request({
url,
method,
data: method === 'GET' ? undefined : options.data,
header: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
},
success: (res) => {
const status = res.statusCode
const body = res.data
if (status === 401) {
onUnauthorized()
reject(new Error('登录已过期'))
return
}
if (status < 200 || status >= 300) {
const message = parseDetailMessage(body) || `请求失败(${status})`
if (showError) {
uni.showToast({ title: String(message).slice(0, 40), icon: 'none' })
}
reject(new Error(message))
return
}
if (body && body.code !== undefined && body.code !== 0) {
const message = body.msg || '请求失败'
if (showError) {
uni.showToast({ title: String(message).slice(0, 40), icon: 'none' })
}
reject(new Error(message))
return
}
resolve(body)
},
fail: (err) => {
if (showError) {
uni.showToast({ title: '网络异常', icon: 'none' })
}
reject(err)
}
})
})
}
/**
* 附件 multipart 上传(uni.uploadFile)
* @param {string} filePath
* @param {{ bizType?: string, bizId?: string|number, showError?: boolean }} opts
*/
export function bxUploadFile(filePath, opts = {}) {
const showError = opts.showError !== false
const token = uni.getStorageSync(TOKEN_KEY) || ''
const query = buildQuery({
biz_type: opts.bizType || '',
biz_id: opts.bizId != null && opts.bizId !== '' ? opts.bizId : 0
})
const url = `${resolveApiBase()}/attachments/upload${query}`
return new Promise((resolve, reject) => {
uni.uploadFile({
url,
filePath,
name: 'file',
header: {
...(token ? { Authorization: `Bearer ${token}` } : {})
},
success: (res) => {
const status = res.statusCode
let body = res.data
if (typeof body === 'string') {
try {
body = JSON.parse(body)
} catch (e) {
body = {}
}
}
if (status === 401) {
onUnauthorized()
reject(new Error('登录已过期'))
return
}
if (status < 200 || status >= 300) {
const message = parseDetailMessage(body) || `上传失败(${status})`
if (showError) uni.showToast({ title: String(message).slice(0, 40), icon: 'none' })
reject(new Error(message))
return
}
if (body && body.code !== undefined && body.code !== 0) {
const message = body.msg || '上传失败'
if (showError) uni.showToast({ title: String(message).slice(0, 40), icon: 'none' })
reject(new Error(message))
return
}
resolve(body)
},
fail: (err) => {
if (showError) uni.showToast({ title: '上传失败', icon: 'none' })
reject(err)
}
})
})
}
/**
* 发票 multipart 上传(POST /invoices,name=files)
* 对齐 bx-mobile FormData:amount / remark / files / targetUserId
* @param {{ amount: number|string, remark?: string, filePath: string, targetUserId?: string|number, showError?: boolean }} opts
*/
export function bxUploadInvoice(opts = {}) {
const showError = opts.showError !== false
const token = uni.getStorageSync(TOKEN_KEY) || ''
const filePath = opts.filePath
if (!filePath) {
return Promise.reject(new Error('请选择发票文件'))
}
const formData = {
amount: String(opts.amount ?? '')
}
if (opts.remark != null && String(opts.remark).trim()) {
formData.remark = String(opts.remark).trim()
}
if (opts.targetUserId != null && opts.targetUserId !== '') {
formData.targetUserId = String(opts.targetUserId)
}
const url = `${resolveApiBase()}/invoices`
return new Promise((resolve, reject) => {
uni.uploadFile({
url,
filePath,
name: 'files',
formData,
header: {
...(token ? { Authorization: `Bearer ${token}` } : {})
},
success: (res) => {
const status = res.statusCode
let body = res.data
if (typeof body === 'string') {
try {
body = JSON.parse(body)
} catch (e) {
body = {}
}
}
if (status === 401) {
onUnauthorized()
reject(new Error('登录已过期'))
return
}
if (status < 200 || status >= 300) {
const message = parseDetailMessage(body) || `上传失败(${status})`
if (showError) uni.showToast({ title: String(message).slice(0, 40), icon: 'none' })
reject(new Error(message))
return
}
if (body && body.code !== undefined && body.code !== 0) {
const message = body.msg || '上传失败'
if (showError) uni.showToast({ title: String(message).slice(0, 40), icon: 'none' })
reject(new Error(message))
return
}
resolve(body)
},
fail: (err) => {
if (showError) uni.showToast({ title: '上传失败', icon: 'none' })
reject(err)
}
})
})
}
export default bxRequest