把这些逻辑写在model里,调用只传 logintype javascript
// fetch.ts
import Taro from '@tarojs/taro'
/** * 上传、下载 和普通请求的判断 todo */
import { checkTokenValid, refreshToken, codeMessage, getStorage } from './index'
// const loginUrl = process.env.login_url;
// const api_url = process.env.api_url;
const api_url = 'https://likecrm-api.creams.io/'
export interface Options {
header?: HeadersInit
showToast?: boolean
noToken?: boolean
dataType?: String
data?: any
responseType?: String
success?: Function
fail?: Function
complete?: Function
callBack?: Function
}
export default async function fetch<T>( urlSuffix: String, method: String = 'GET', options: Options ): Promise<T> {
// 设置token
const defaultOptions: any = {
header: {},
noToken: false, // 临时不用token
showToast: true,
data: {},
}
const currentOptions = {
...defaultOptions,
...options,
}
// 若是是游客 不设置token
const loginType = await getStorage('loginType');
if (loginType === 'VISITOR') {
currentOptions.header.Authorization = ``;
return _fetch<T>(urlSuffix, method, currentOptions)
}
if (!currentOptions.noToken) {
const accessToken = await getStorage('accessToken')
currentOptions.header.Authorization = `Bearer ${accessToken}`
const tokenValid = await checkTokenValid()
// if (tokenValid) {
return _fetch<T>(urlSuffix, method, currentOptions);
// }
// return refreshToken<T>(_fetch, urlSuffix, method, currentOptions)
}
return _fetch<T>(urlSuffix, method, currentOptions)
}
// 设置请求头 不包括 token
const addRequestHeader = async function(requestOption) {
const methods = ['POST', 'PUT', 'DELETE']
if (methods.includes(requestOption.method)) {
// 小程序 没有 FormData 对象 "application/x-www-form-urlencoded"
requestOption.header = {
Accept: 'application/json',
'content-Type': 'application/json; charset=utf-8',
...requestOption.header,
}
requestOption.data = JSON.stringify(requestOption.data)
}
return requestOption
}
// 过滤请求结果
const checkStatusAndFilter = (response): Promise<any> | undefined => {
if (response.statusCode >= 200 && response.statusCode < 300) {
return response.data
} else {
const errorText = codeMessage[response.statusCode] || response.errMsg
const error = response.data.error
return Promise.reject({ ...response, errorText, error })
}
}
// 正式请求
async function _fetch<T>( urlSuffix: Request | String, method: String = 'GET', options: Options ): Promise<T> {
const { showToast = true, ...newOption } = options
if (showToast) {
Taro.showLoading({
title: '加载中',
})
}
const url = `${api_url}${urlSuffix}`
const defaultRequestOption: Object = {
url,
method,
...newOption,
}
const requestOption = await addRequestHeader(defaultRequestOption)
try {
return await Taro.request(requestOption)
.then(checkStatusAndFilter)
.then(res => {
Taro.hideLoading()
if (newOption.callBack) {
newOption.callBack(res)
}
return res
})
.catch(response => {
// if (response.statusCode === 401) {
// Taro.hideLoading()
// return response
// // 登录能够拦截
// // refreshToken<T>(_fetch, urlSuffix, method, options);
// } else {
Taro.hideLoading()
if (requestOption.showResponse) {
// 自定义 错误结果
return response
}
Taro.showToast({
title: response.errorText,
icon: 'none',
duration: 2000,
})
return response.data
// }
})
} catch (e) {
Taro.hideLoading()
Taro.showToast({
title: '代码执行异常',
mask: true,
icon: 'none',
duration: 2000,
})
return Promise.reject()
}
}
复制代码
// checkTokenValid.ts
import Taro from '@tarojs/taro'
import { CLIENT_ID, APPROACHING_EFFECTIVE_TIME, TRY_LOGIN_LIMIT } from '@/constants/index'
import {
setStorageArray,
getStorageArray,
removeStorageArray,
getStorage,
isError,
Options,
} from './index'
import {
PostOauth2LoginRefreshTokenQuery,
postOauth2LoginRefreshToken,
postOauth2PlatformLogin,
} from '@/actions/crm-user/UserLogin'
type IRequest<T> = (urlSuffix: Request | string, method: String, options?: Options) => Promise<T>
let delayedFetches: any = [] //延迟发送的请求
let isCheckingToken = false //是否在检查token
let tryLoginCount = 0 // 尝试登录次数
// 检验token是否快过时;
const checkTokenValid = async () => {
const [tokenTimestamp, oldTimestamp, refreshToken] = await getStorageArray([
'tokenTimestamp',
'oldTimestamp',
'refreshToken',
])
const nowTimestamp = Date.parse(String(new Date())) // 当前时间
const EffectiveTimes = tokenTimestamp ? tokenTimestamp * 1000 : APPROACHING_EFFECTIVE_TIME // 有效时间
const oldTimes = oldTimestamp ? oldTimestamp : nowTimestamp // 注册时间
const valid =
nowTimestamp - oldTimes <= EffectiveTimes - APPROACHING_EFFECTIVE_TIME && refreshToken
? true
: false
return valid
}
async function refreshToken<T>( fetchWithoutToken: IRequest<T>, urlSuffix: Request | String, method: String, options?: Options ): Promise<T> {
return new Promise(async (resolve, reject) => {
delayedFetches.push({
urlSuffix,
method,
options,
resolve,
reject,
})
if (!isCheckingToken) {
isCheckingToken = true
const refreshTokenStorage = (await getStorage('refreshToken')) as string
const query: PostOauth2LoginRefreshTokenQuery = {
clientId: CLIENT_ID,
refreshToken: refreshTokenStorage,
}
postOauth2LoginRefreshToken({
query,
noToken: true,
showResponse: true,
}).then(async data => {
const error = isError(data) as any
if (error) {
// 登录态失效报401(token失效的话),且重试次数未达到上限
if (
(error.statusCode < 200 || error.statusCode >= 300) &&
tryLoginCount < TRY_LOGIN_LIMIT
) {
// 登陆超时 && 从新登陆
await removeStorageArray([
'accessToken',
'refreshToken',
'tokenTimestamp',
'oldTimestamp',
'userId',
])
const loginInfo = await Taro.login()
const login = async () => {
try {
if (tryLoginCount < TRY_LOGIN_LIMIT) {
const response = await postOauth2PlatformLogin({
query: {
clientId: CLIENT_ID,
code: loginInfo.code,
loginType: 'OFFICIAL',
},
body: {},
noToken: true,
})
const userAccessTokenModel = response.userAccessTokenModel
const oldTimestamp = Date.parse(String(new Date()))
await setStorageArray([
{
key: 'accessToken',
data: userAccessTokenModel!.access_token,
},
{
key: 'refreshToken',
data: userAccessTokenModel!.refresh_token,
},
{
key: 'tokenTimestamp',
data: userAccessTokenModel!.expires_in,
},
{ key: 'oldTimestamp', data: oldTimestamp },
])
tryLoginCount = 0
} else {
Taro.redirectTo({
url: '/pages/My/Authorization/index',
})
}
} catch (e) {
tryLoginCount++
login()
}
login()
}
} else if (tryLoginCount >= TRY_LOGIN_LIMIT) {
Taro.redirectTo({
url: '/pages/My/Authorization/index',
})
} else {
Taro.showToast({
title: error.errorText,
icon: 'none',
duration: 2000,
complete: logout,
})
}
return
}
if (data.access_token && data.refresh_token) {
const oldTimestamp = Date.parse(String(new Date()))
await setStorageArray([
{ key: 'accessToken', data: data.access_token },
{ key: 'refreshToken', data: data.refresh_token },
{ key: 'tokenTimestamp', data: data.expires_in },
{ key: 'oldTimestamp', data: oldTimestamp },
])
delayedFetches.forEach(fetch => {
return fetchWithoutToken(fetch.urlSuffix, fetch.method, replaceToken(fetch.options))
.then(fetch.resolve)
.catch(fetch.reject)
})
delayedFetches = []
}
isCheckingToken = false
})
} else {
// 正在登检测中,请求轮询稍后,避免重复调用登检测接口
setTimeout(() => {
refreshToken(fetchWithoutToken, urlSuffix, method, options)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
}, 1000)
}
})
}
function logout() {
// window.localStorage.clear();
// window.location.href = `${loginUrl}/logout`;
}
function replaceToken(options: Options = {}): Options {
if (!options.noToken && options.header && (options.header as any).Authorization) {
getStorage('accessToken').then(accessToken => {
;(options.header as any).Authorization = `Bearer ${accessToken}`
})
}
return options
}
export { checkTokenValid, refreshToken }
复制代码
/** * navigateTo 超过8次以后 强行进行redirectTo 不然会形成页面卡死 */
const nav = Taro.navigateTo
Taro.navigateTo = data => {
if (Taro.getCurrentPages().length > 8) {
return Taro.redirectTo(data)
}
return nav(data)
}
复制代码
小程序构建骨架屏的探索
React 中同构(SSR)原理脉络梳理
react服务端渲染demo (基于Dva)
1: 问一下ui 须要多少页面写骨架屏 采用哪一种方法css
首个 Taro 多端统一实例 - 网易严选
用 React 编写的基于Taro + Dva构建的适配不一样端html
参考java