Files
xk-doctor-wx/common/js/index.js

331 lines
10 KiB
JavaScript
Raw Normal View History

2024-09-19 12:57:55 +08:00
import Request from './request.js';
import errorCode from './errorCode.js';
2025-02-13 10:31:01 +08:00
import {Base64} from "js-base64";
import { getDoctorWxEnv } from '@/config/app-env.js';
2026-07-03 07:57:26 +08:00
import { resolveDoctorWxAuthToken } from '@/utils/doctorWxAuthToken.js';
const { legacyApiBase } = getDoctorWxEnv();
2024-09-19 12:57:55 +08:00
export const config = {
baseUrl: legacyApiBase
2024-09-19 12:57:55 +08:00
}
2025-02-13 10:31:01 +08:00
const arr = [
'mobile',
'call_mobile',
2025-02-13 10:31:01 +08:00
'express_mobile',
'doctor',
'accept_tel',
'patient_mobile',
'id_card',
'idcard',
2026-06-05 12:28:36 +08:00
'password',
'phone'
2025-02-13 10:31:01 +08:00
]
// 敏感数据(解密后做脱敏;需与下方 switch 分支一致)
// 注意call_mobile 只解密不脱敏,供回访拨号使用
2025-02-13 10:31:01 +08:00
const sensitiveData = [
'id_card',
'idcard',
'mobile',
'express_mobile',
2026-06-05 12:28:36 +08:00
'patient_mobile',
'phone'
2025-02-13 10:31:01 +08:00
]
2024-09-19 12:57:55 +08:00
/**
* 请求拦截器
2024-12-21 17:10:32 +08:00
* @param {*} options
2024-09-19 12:57:55 +08:00
*/
const reqInterceptor = async (options) => {
2025-02-13 10:31:01 +08:00
// 加密
if (options.data != null && options.type !== 'upload') {
2025-02-13 10:31:01 +08:00
options.data = getRes(options.data, false)
}
2024-09-19 12:57:55 +08:00
options.header = {
...options.header
}
const skipToken = options.header.isToken === false
const isClinicAdminReq = options.header._clinicAdmin === '1'
|| (options.url && String(options.url).indexOf('clinic-admin') !== -1)
2026-06-05 12:28:36 +08:00
const isPlatformAdminReq = options.header._platformAdmin === '1'
|| (options.url && String(options.url).indexOf('platform-admin') !== -1)
const isSalespersonReq = options.header._salesperson === '1'
|| (options.url && String(options.url).indexOf('salesperson-') !== -1)
const isClinicSalespersonReq = options.header._clinicSalesperson === '1'
|| (options.url && String(options.url).indexOf('clinic-salesperson-') !== -1)
const isDoctorWxAuthReq = options.url && String(options.url).indexOf('doctor-wx-auth') !== -1
// 内部控制位,不得作为 HTTP 头发出
delete options.header.isToken
delete options.header._clinicAdmin
2026-06-05 12:28:36 +08:00
delete options.header._platformAdmin
delete options.header._salesperson
delete options.header._clinicSalesperson
const clinicToken = uni.getStorageSync('clinic_admin_token')
2026-06-05 12:28:36 +08:00
const platformToken = uni.getStorageSync('platform_admin_token')
const salespersonToken = uni.getStorageSync('salesperson_token')
const clinicSalespersonToken = uni.getStorageSync('clinic_salesperson_token')
const doctorToken = uni.getStorageSync('token')
const loginMode = uni.getStorageSync('loginMode')
if (!skipToken) {
if (isDoctorWxAuthReq) {
2026-07-03 07:57:26 +08:00
const authToken = resolveDoctorWxAuthToken()
if (authToken) {
options.header['authorization'] = `Bearer ${authToken}`
}
2026-06-05 12:28:36 +08:00
} else if (isPlatformAdminReq && platformToken) {
options.header['authorization'] = `Bearer ${platformToken}`
} else if (isSalespersonReq && salespersonToken) {
options.header['authorization'] = `Bearer ${salespersonToken}`
} else if (isClinicSalespersonReq && clinicSalespersonToken) {
options.header['authorization'] = `Bearer ${clinicSalespersonToken}`
} else if (isClinicAdminReq && clinicToken) {
options.header['authorization'] = `Bearer ${clinicToken}`
} else if (doctorToken) {
options.header['authorization'] = `Bearer ${doctorToken}`
}
2024-09-19 12:57:55 +08:00
}
return options;
}
/**
* 登录失效统一处理弹窗提示并按 loginMode 清理 token 后跳转登录页
* @param {string} tipMsg 提示文案
* @param {*} response 原始响应
* @param {object} conf 请求配置 _responseLog 使用
*/
const handleAuthExpired = (tipMsg, response, conf) => {
// 防重复弹窗:并发 401 只提示一次
if (!uni.__xkAuthModalShowing) {
uni.__xkAuthModalShowing = true
uni.showModal({
title: '登录失效',
content: tipMsg,
showCancel: false,
confirmText: '重新登录',
success() {
const loginMode = uni.getStorageSync('loginMode')
if (loginMode === 'platform_admin') {
uni.removeStorageSync('platform_admin_token')
uni.removeStorageSync('platform_admin_user')
uni.removeStorageSync('loginMode')
} else if (loginMode === 'salesperson') {
uni.removeStorageSync('salesperson_token')
uni.removeStorageSync('salesperson_user')
uni.removeStorageSync('loginMode')
} else if (loginMode === 'clinic_salesperson') {
uni.removeStorageSync('clinic_salesperson_token')
uni.removeStorageSync('clinic_salesperson_user')
uni.removeStorageSync('loginMode')
} else if (loginMode === 'clinic_admin') {
uni.removeStorageSync('clinic_admin_token')
uni.removeStorageSync('clinic_admin_user')
uni.removeStorageSync('loginMode')
} else {
uni.removeStorageSync('token')
uni.removeStorageSync('loginInfo')
uni.removeStorageSync('identity')
}
uni.__xkAuthModalShowing = false
uni.reLaunch({ url: '/pages/login/index' })
},
fail() {
uni.__xkAuthModalShowing = false
}
})
}
_responseLog(response, conf, "response 401")
return {
wakaryReqToReject: true,
msg: tipMsg,
res: response
}
}
2024-09-19 12:57:55 +08:00
/**
* 响应拦截器
*/
const resInterceptor = (response, conf = {}) => {
// 网络状态码
const statusCode = response.statusCode|| response.errcode;
// 响应拦截器
if (statusCode >= 200 && statusCode < 300) {
_responseLog(response, conf, "response 200-299")
// 解密Yii业务在 dataLaravel jok业务在 result
2025-02-13 10:31:01 +08:00
let res = response.data
if (res == null || typeof res !== 'object') {
return res
}
2025-02-13 10:31:01 +08:00
if (res.data != null) {
res.data = getRes(res.data)
}
if (res.result != null) {
res.result = getRes(res.result)
}
// Laravel notAuth 返回 HTTP 200 + 业务码 401需与 HTTP 401 走同一套登录失效处理
const bizCode = Number(res.code != null ? res.code : res.errcode)
if (bizCode === 401) {
return handleAuthExpired(res.msg || res.message || '账号需重新登录', response, conf)
}
2025-02-13 10:31:01 +08:00
return res
2024-09-19 12:57:55 +08:00
} else if (statusCode === 500) {
uni.showToast({
icon: 'error',
title: response['msg'] || errorCode[statusCode]
})
_responseLog(response, conf, "response 500")
// 增加一个控制字段wakaryReqToReject 使 reject的内容更加可控
return {
// 根据当前字段来判断是否reject
wakaryReqToReject: true,
// 下面可以配置其他返回信息方便统一处理reject
// 以下内容作为reject的返回根据需求处理返回具体错误信息
msg: "服务器错误",
res: response
}
} else if (statusCode === 401) {
return handleAuthExpired(response['msg'] || response['message'] || errorCode[statusCode] || '账号需重新登录', response, conf)
2024-09-19 12:57:55 +08:00
} else {
uni.showToast({
icon: 'error',
title: response['msg'] || errorCode[statusCode]
})
_responseLog(response, conf, "response 300-499");
// 增加一个控制字段wakaryReqToReject 使 reject的内容更加可控
return {
// 根据当前字段来判断是否reject
wakaryReqToReject: true,
// 下面可以配置其他返回信息方便统一处理reject
// 以下内容作为reject的返回根据需求处理返回具体错误信息
msg: "response 300-499",
res: response
}
}
}
/**
* request log
*/
function _requestLog(req, describe = null) {
if (process.env.NODE_ENV === 'development') {}
}
/**
* response log
*/
function _responseLog(res, conf = {}, describe = null) {
let _statusCode = res.statusCode;
if (process.env.NODE_ENV === 'development') {}
if (_statusCode === 500) {
// save log to server
}
}
2025-02-13 10:31:01 +08:00
function getRes(obj, isDecode = true) {
if (obj == null || typeof obj !== 'object') {
return obj
}
2025-02-13 10:31:01 +08:00
try {
for (const key in obj) {
if (Array.isArray(obj[key])) {
obj[key] = getRes(obj[key])
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
2025-02-13 10:31:01 +08:00
obj[key] = getRes(obj[key])
} else {
// 判断obj[key]是否在arr中
if (arr.indexOf(key) !== -1) {
2026-06-05 12:28:36 +08:00
if (obj[key] == null || obj[key] === '') continue
2025-02-13 10:31:01 +08:00
let aseFile = ''
if (isDecode === true) {
aseFile = customBase64Decode(obj[key])
} else {
aseFile = customBase64Encode(obj[key])
}
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
typeof aseFile === 'string' ? aseFile : ''
)
if (isGarbled) {
aseFile = obj[key]
}
if (isDecode === true) {
if (sensitiveData.indexOf(key) !== -1) {
// 数据脱敏,自动匹配姓名、手机号、身份证
switch (key) {
case 'express_name':
case 'express_region':
case 'accept_name':
case 'patient':
// 保留前两个字符,其余用星号代替
aseFile = aseFile.slice(0, 1).padEnd(aseFile.length, '*');
break;
case 'mobile':
case 'express_mobile':
2026-06-05 12:28:36 +08:00
case 'phone':
2025-02-13 10:31:01 +08:00
// 保留前三位和后四位,中间用星号代替
aseFile = aseFile.slice(0, 3).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
break;
case 'id_card':
case 'idcard':
// 保留前六位和后四位,中间用星号代替
aseFile = aseFile.slice(0, 6).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
break;
default:
// 默认情况下,全部用星号代替
break;
}
}
}
obj[key] = aseFile
}
}
}
return obj
} catch (error) {
console.error('错误', error)
return obj
}
}
function customBase64Decode(data) {
2026-06-05 12:28:36 +08:00
if (data == null || data === '') return data
2025-02-13 10:31:01 +08:00
// return data
try {
// 第一次Base64解码
const decodedFirst = Base64.decode(data)
// 截取从第30个字符开始往后的内容
const subStr = decodedFirst.slice(30)
// 第二次Base64解码
return Base64.decode(subStr) != ''? Base64.decode(subStr): data
} catch (error) {
console.error(`解码Base64字符串【${data}】时发生错误`, error)
return data
}
}
// 加密
function customBase64Encode(data) {
if (data == null || data === '') return data
// return data
try {
// 和加密方法差不多但是是要生成30个随机字符
let randomString = ''
for (let i = 0; i < 30; i++) {
const randomChar = String.fromCharCode(Math.floor(Math.random() * (126 - 32 + 1)) + 32)
randomString += randomChar
}
const encoded = Base64.encode(data)
return Base64.encode(randomString + encoded)
} catch (error) {
console.error(`加密字符串【${data}】时发生错误`, error)
return data
}
}
2024-09-19 12:57:55 +08:00
/**
* 请求方法封装
*/
2024-12-21 17:10:32 +08:00
2024-09-19 12:57:55 +08:00
export const req = new Request(config, reqInterceptor, resInterceptor)