把這些邏輯寫在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