Merge branch 'master' of https://gitea.zianxin.com/zax/biz-mobile-react-cloud
This commit is contained in:
commit
e1ac23b5af
8 files changed
+372
-182
No files matched your search
+2
-1
@@ -179,7 +179,8 @@ export default {
|
||||
if (lp) {
|
||||
uni.setStorageSync("launchParam", lp)
|
||||
}
|
||||
const { tryZzdEntryLogin } = await import("@/sheep/utils/request")
|
||||
const { sanitizeBizSession, tryZzdEntryLogin } = await import("@/sheep/utils/request")
|
||||
sanitizeBizSession()
|
||||
await tryZzdEntryLogin()
|
||||
const { onZzdAppLaunchTrack } = await import("@/sheep/utils/zzd-aplus-track")
|
||||
await onZzdAppLaunchTrack()
|
||||
|
||||
@@ -224,7 +224,17 @@ const jumpByParam = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const doRetry = () => {
|
||||
const doRetry = async () => {
|
||||
try {
|
||||
const { clearBizSession } = await import("@/sheep/utils/request")
|
||||
// 清掉脏 token / 错误 userRole,效果接近卸载重装里「清 WebView 登录态」
|
||||
clearBizSession({ clearRole: true })
|
||||
} catch (e) {
|
||||
console.warn("[doRetry] clearBizSession", e)
|
||||
}
|
||||
jumping = false
|
||||
bootStatus.value = "loading"
|
||||
bootMessage.value = "加载中..."
|
||||
void jumpByParam()
|
||||
}
|
||||
|
||||
@@ -251,7 +261,8 @@ const doModuleJump = (item: any) => {
|
||||
<view v-if="bootStatus === 'loading'" class="text-center sub-text-30 color-#666">加载中...</view>
|
||||
<view v-else class="text-center">
|
||||
<view class="sub-text-30 color-#333 mb-40rpx lh-1.5">{{ bootMessage }}</view>
|
||||
<view class="bg-#0094ff color-white! rounded-40rpx py-20rpx px-60rpx inline-block" @tap="doRetry">重试</view>
|
||||
<view class="second-text-26 color-#999 mb-30rpx">若多次失败,将清除本地登录缓存后重试</view>
|
||||
<view class="bg-#0094ff color-white! rounded-40rpx py-20rpx px-60rpx inline-block" @tap="doRetry">清除缓存并重试</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
+19
-31
@@ -87,6 +87,7 @@ import { hooks, router, constant } from "@@/index"
|
||||
import { actCancel, userApi } from "@@/api"
|
||||
import { isDevEnv } from "@/sheep/utils/app"
|
||||
import { APPENV } from "@/sheep/constant"
|
||||
import { ensureBizLogin } from "@/sheep/utils/request"
|
||||
|
||||
import ModifyUser from "@@/components/modify-user.vue"
|
||||
|
||||
@@ -167,48 +168,35 @@ const storage = {
|
||||
}
|
||||
|
||||
const login = {
|
||||
// 检查应用环境并登录
|
||||
// 检查应用环境并登录(浙政钉走 getAuthCode/zzdsso,诗画走 authsh;勿再只认 URL uabaseToken)
|
||||
checkAppInfo: async () => {
|
||||
try {
|
||||
// 先尝试诗画登录
|
||||
const shihuaResult = await login.shihuaLogin()
|
||||
if (shihuaResult) return
|
||||
|
||||
// 尝试浙政钉登录
|
||||
const zhengdingResult = login.zhengdingLogin()
|
||||
if (zhengdingResult) return
|
||||
|
||||
uni.showToast({ title: "当前登录环境未知", icon: "none" })
|
||||
const appInfo = await getAppInfo()
|
||||
if (appInfo !== APPENV.ZZD && appInfo !== APPENV.SH) {
|
||||
uni.showToast({ title: "当前登录环境未知", icon: "none" })
|
||||
return
|
||||
}
|
||||
const ok = await ensureBizLogin()
|
||||
if (ok) {
|
||||
storage.getUserInfo()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("登录失败:", error)
|
||||
uni.showToast({ title: "登录失败", icon: "none" })
|
||||
}
|
||||
},
|
||||
|
||||
// 诗画环境登录
|
||||
// 保留:开发/兼容旧调用;生产点「请先登录」走 checkAppInfo → ensureBizLogin
|
||||
shihuaLogin: async () => {
|
||||
const appInfo = await (window as any).CP2.getAppInfo()
|
||||
if (appInfo.code == 0) return false
|
||||
|
||||
const userInfo = await (window as any).CP2.getUserInfo()
|
||||
if (userInfo.code == 0) {
|
||||
uni.showToast({ title: "获取诗画用户信息失败", icon: "none" })
|
||||
return false
|
||||
} else {
|
||||
const { accountId, sessionId } = userInfo.data
|
||||
return login.doLogin(1, "", accountId, sessionId)
|
||||
// return login.doLogin(1, "", "67186e49d63fea141aad6772", "67186e49d63fea141aad6776")
|
||||
}
|
||||
const env = await getAppInfo()
|
||||
if (env !== APPENV.SH) return false
|
||||
return await ensureBizLogin()
|
||||
},
|
||||
|
||||
// 浙政钉登录
|
||||
zhengdingLogin: () => {
|
||||
const params = new URL(window.location.href).searchParams
|
||||
const token = params.get("uabaseToken")
|
||||
if (token) {
|
||||
return login.doLogin(2, token, "", "")
|
||||
}
|
||||
return false
|
||||
zhengdingLogin: async () => {
|
||||
const env = await getAppInfo()
|
||||
if (env !== APPENV.ZZD) return false
|
||||
return await ensureBizLogin()
|
||||
},
|
||||
|
||||
// 执行登录操作 - loginType 1:诗画登录 2:浙政钉登录
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
<view class="w-full text-center mt-20">
|
||||
<image src="@/static/page/happyMall.png" class="rounded-full w-1/3" mode="widthFix" />
|
||||
<view>美好商城</view>
|
||||
<view v-if="loginError" class="second-text-28 m-t-20rpx" style="color: #e54d42; padding: 0 40rpx">
|
||||
{{ loginError }}
|
||||
</view>
|
||||
<view class="h-100rpx" />
|
||||
<tm-button @click="show = true">继续跳转</tm-button>
|
||||
<tm-button v-if="!loginError" @click="show = true">继续跳转</tm-button>
|
||||
<tm-button v-else @click="retryLogin">清除缓存并重试</tm-button>
|
||||
</view>
|
||||
|
||||
<tm-modal v-model:show="show" :overlayClick="false" title="注意" position="bottom" height="auto"
|
||||
@@ -19,8 +23,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { hooks } from "@@/index"
|
||||
import { merchantApi } from "@@/api"
|
||||
import { clearBizSession, ensureBizLogin, recoverBizSession } from "@@/utils/request"
|
||||
// import { filePath } from "@@/utils/file"
|
||||
|
||||
// const params = reactive<any>({
|
||||
@@ -28,12 +32,40 @@ import { merchantApi } from "@@/api"
|
||||
// })
|
||||
|
||||
const show = ref(false)
|
||||
const loginError = ref("")
|
||||
|
||||
const ensureLoginAndMaybeOpen = async (autoOpenModal: boolean) => {
|
||||
loginError.value = ""
|
||||
try {
|
||||
const ok = await ensureBizLogin()
|
||||
if (ok) {
|
||||
if (autoOpenModal) show.value = true
|
||||
return
|
||||
}
|
||||
loginError.value = "登录失败,可点下方清除缓存后重试"
|
||||
} catch (e: any) {
|
||||
loginError.value = (e && e.message) || "登录异常,请重试"
|
||||
}
|
||||
}
|
||||
|
||||
const retryLogin = async () => {
|
||||
loginError.value = ""
|
||||
try {
|
||||
clearBizSession({ clearRole: true })
|
||||
const ok = await recoverBizSession()
|
||||
if (ok) {
|
||||
show.value = true
|
||||
return
|
||||
}
|
||||
loginError.value = "仍无法登录,请关闭后从工作台重新进入"
|
||||
} catch (e: any) {
|
||||
loginError.value = (e && e.message) || "登录异常,请重试"
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
show.value = true
|
||||
}
|
||||
// 门户直接跳本页时本地未必有 token,需主动走浙政钉/诗画登录,不能只用 useCheckLogin 静默判断
|
||||
await ensureLoginAndMaybeOpen(true)
|
||||
})
|
||||
|
||||
// onShow(() => {
|
||||
|
||||
@@ -2,14 +2,23 @@ import storageConstant from "../constant/storage-constant"
|
||||
import { jump } from "../router/index"
|
||||
import { isDevEnv } from "../utils/app"
|
||||
|
||||
function readBearer(tokenStorage: any): string {
|
||||
if (!tokenStorage || typeof tokenStorage !== "object" || tokenStorage.data == -1) return ""
|
||||
const pick = (v: unknown): string => {
|
||||
if (typeof v === "string" && v.trim()) return v.trim()
|
||||
if (v && typeof v === "object" && typeof (v as { tokenValue?: unknown }).tokenValue === "string") {
|
||||
return (v as { tokenValue: string }).tokenValue.trim()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
const raw = pick(tokenStorage.accessToken) || pick(tokenStorage.access_token)
|
||||
return raw ? `Bearer ${raw}` : ""
|
||||
}
|
||||
|
||||
export const useToken = () => {
|
||||
const tokenKey = storageConstant.TOKEN
|
||||
const tokenStorage = uni.getStorageSync(tokenKey)
|
||||
|
||||
if (isDevEnv()) {
|
||||
return tokenStorage?.access_token ? `Bearer ${tokenStorage.access_token}` : ""
|
||||
}
|
||||
return tokenStorage?.accessToken?.tokenValue ? `Bearer ${tokenStorage.accessToken.tokenValue}` : ""
|
||||
return readBearer(tokenStorage)
|
||||
}
|
||||
|
||||
export const useDefaultHeader = () => {
|
||||
@@ -24,15 +33,15 @@ export const useDefaultHeader = () => {
|
||||
export const useStaticCheckLogin = () => {
|
||||
const tokenKey = storageConstant.TOKEN
|
||||
const tokenStorage = uni.getStorageSync(tokenKey)
|
||||
if (tokenStorage) return true
|
||||
return false
|
||||
return !!readBearer(tokenStorage)
|
||||
}
|
||||
|
||||
export const useCheckLogin = async (): Promise<boolean> => {
|
||||
const tokenKey = storageConstant.TOKEN
|
||||
const tokenStorage = uni.getStorageSync(tokenKey)
|
||||
const loggedIn = !!readBearer(tokenStorage)
|
||||
if (isDevEnv()) {
|
||||
if (tokenStorage) return true
|
||||
if (loggedIn) return true
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "您还未登录,请先登录",
|
||||
@@ -52,7 +61,7 @@ export const useCheckLogin = async (): Promise<boolean> => {
|
||||
})
|
||||
return false
|
||||
} else {
|
||||
if (tokenStorage) return true
|
||||
if (loggedIn) return true
|
||||
uni.showToast({ title: "你还未登录", icon: "none" })
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -45,11 +45,16 @@ export const getAppInfo = async (): Promise<string> => {
|
||||
if (cp2?.getAppInfo) {
|
||||
const res = await cp2.getAppInfo()
|
||||
if (res?.code === 0) {
|
||||
console.log("环境:浙政钉-zzd")
|
||||
return APPENV.ZZD
|
||||
// index.html 引入的 jcommind 在普通浏览器也可能 code=0,须同时有浙政钉容器特征
|
||||
if (looksLikeZzdContainer()) {
|
||||
console.log("环境:浙政钉-zzd")
|
||||
return APPENV.ZZD
|
||||
}
|
||||
console.warn("[getAppInfo] CP2.code=0 但无浙政钉容器特征,继续探测")
|
||||
} else {
|
||||
console.log("环境:诗画浦江-sh")
|
||||
return APPENV.SH
|
||||
}
|
||||
console.log("环境:诗画浦江-sh")
|
||||
return APPENV.SH
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[getAppInfo] CP2.getAppInfo 失败,回退容器探测", error)
|
||||
|
||||
@@ -6,6 +6,8 @@ const JSAPI_LOAD_TIMEOUT_MS = 45000
|
||||
const DEFAULT_OPEN_JS_URL = "https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.12/dingtalk.open.js"
|
||||
/** 微应用需 dd.config 完成后 ready 才会回调;超时则不再盲调 requestAuthCode */
|
||||
const DD_READY_TIMEOUT_MS = 30000
|
||||
/** gdt getAuthCode 无回调时避免「登录中」遮罩一直盖住页面(白屏感) */
|
||||
const GET_AUTH_CODE_TIMEOUT_MS = 20000
|
||||
|
||||
let ddErrorCallbackRegistered = false
|
||||
|
||||
@@ -181,7 +183,15 @@ export function authCodeFromGdtResult(res: unknown): string | null {
|
||||
*/
|
||||
export async function requestZzdAuthCodeViaGdt(corpId: string): Promise<string> {
|
||||
await loadDingTalkOpenJsapi()
|
||||
const res = await gdtJsapi.getAuthCode({ corpId: (corpId || "").trim() })
|
||||
const res = await Promise.race([
|
||||
gdtJsapi.getAuthCode({ corpId: (corpId || "").trim() }),
|
||||
new Promise<never>((_, rej) =>
|
||||
setTimeout(
|
||||
() => rej(new Error(`getAuthCode 超时(${GET_AUTH_CODE_TIMEOUT_MS / 1000}s),请关闭后重新进入`)),
|
||||
GET_AUTH_CODE_TIMEOUT_MS,
|
||||
),
|
||||
),
|
||||
])
|
||||
const code = authCodeFromGdtResult(res)
|
||||
if (!code) throw new Error("getAuthCode 未返回有效 code(请核对 gdt 返回字段)")
|
||||
return code
|
||||
|
||||
+262
-128
@@ -8,11 +8,75 @@ import { APPENV } from "../constant"
|
||||
|
||||
const TOKEN_KEY = constant.storageConstant.TOKEN
|
||||
const USER_INFO_KEY = constant.storageConstant.USERINFO
|
||||
const USER_ROLE_KEY = constant.storageConstant.USERROLE
|
||||
const SKIP_AUTH_URLS = ["/thirdparty/thirdPartyOAth2", "/sys/auth/refresh-token", "/auth/oauth2/token"]
|
||||
|
||||
let refreshTokenPromise: Promise<any> | null = null // 存储刷新token的Promise
|
||||
let isRefreshing = false // 是否正在刷新
|
||||
let failedQueue: any[] = [] // 队列存储发送失败的请求
|
||||
/** App.onLaunch 与页面 ensureBizLogin 并发时复用同一次浙政钉登录,避免双重 showLoading/getAuthCode */
|
||||
let zzdLoginPromise: Promise<"ok" | "sms" | "failed"> | null = null
|
||||
/** 拦截器 refreshToken、「我的」checkAppInfo、页面 ensureBizLogin 并发时复用同一次自动登录 */
|
||||
let autoLoginPromise: Promise<boolean> | null = null
|
||||
/** 诗画 /shjnsso 并发互斥(与浙政钉 zzdLoginPromise 对称) */
|
||||
let shLoginPromise: Promise<boolean> | null = null
|
||||
|
||||
/**
|
||||
* 清除本地登录相关缓存(等价于「重装清 WebView」里最关键的几项)。
|
||||
* 默认保留 launchParam(入口参数);诗画卡角色时可 clearRole。
|
||||
*/
|
||||
export function clearBizSession(options?: { clearRole?: boolean; clearLaunchParam?: boolean }) {
|
||||
try {
|
||||
uni.removeStorageSync(TOKEN_KEY)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
uni.removeStorageSync(USER_INFO_KEY)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (options?.clearRole) {
|
||||
try {
|
||||
uni.removeStorageSync(USER_ROLE_KEY)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
if (options?.clearLaunchParam) {
|
||||
const fromUrl = getUrlQueryParam("launchParam")
|
||||
if (!fromUrl) {
|
||||
try {
|
||||
uni.removeStorageSync("launchParam")
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 设备未信任占位,不是可请求的 token */
|
||||
function isDeviceVerifyMarker(tokenStorage: any): boolean {
|
||||
return !!tokenStorage && typeof tokenStorage === "object" && tokenStorage.data == -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动/登录前清理半残 token,避免「有 storage 但带不上 Authorization」导致白屏/进不去。
|
||||
* 保留 data=-1(设备核验)标记。
|
||||
*/
|
||||
export function sanitizeBizSession(): void {
|
||||
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
|
||||
if (isEmpty(tokenStorage)) return
|
||||
if (isDeviceVerifyMarker(tokenStorage)) return
|
||||
if (!getTokenValue(tokenStorage) || isTokenExpires(tokenStorage)) {
|
||||
clearBizSession()
|
||||
}
|
||||
}
|
||||
|
||||
function hasUsableBizToken(tokenStorage: any): boolean {
|
||||
if (isEmpty(tokenStorage) || isDeviceVerifyMarker(tokenStorage)) return false
|
||||
return !!getTokenValue(tokenStorage) && !isTokenExpires(tokenStorage)
|
||||
}
|
||||
|
||||
// 处理队列中的请求
|
||||
const processQueue = (error: any, token: any = null) => {
|
||||
@@ -72,21 +136,14 @@ https.interceptors.request.use(async (config) => {
|
||||
|
||||
// 判断是否需要设置认证头(url 可能是完整地址,故用 includes)
|
||||
if (!SKIP_AUTH_URLS.some((url) => (config.url || "").includes(url))) {
|
||||
if (!isEmpty(tokenStorage)) {
|
||||
// token存在,检查是否过期
|
||||
if (isTokenExpires(tokenStorage)) {
|
||||
// 过期,刷新token
|
||||
const newTokenStorage = await refreshToken()
|
||||
if (newTokenStorage) {
|
||||
const tokenValue = getTokenValue(newTokenStorage)
|
||||
if (tokenValue) config_header.Authorization = tokenValue
|
||||
}
|
||||
} else {
|
||||
const tokenValue = getTokenValue(tokenStorage)
|
||||
if (tokenValue) config_header.Authorization = tokenValue
|
||||
}
|
||||
if (hasUsableBizToken(tokenStorage)) {
|
||||
const tokenValue = getTokenValue(tokenStorage)
|
||||
if (tokenValue) config_header.Authorization = tokenValue
|
||||
} else {
|
||||
// token不存在,刷新token
|
||||
// 无可用 token(含半残/过期/设备核验占位):清理半残后走自动登录
|
||||
if (!isEmpty(tokenStorage) && !isDeviceVerifyMarker(tokenStorage)) {
|
||||
clearBizSession()
|
||||
}
|
||||
const newTokenStorage = await refreshToken()
|
||||
if (newTokenStorage) {
|
||||
const tokenValue = getTokenValue(newTokenStorage)
|
||||
@@ -134,7 +191,9 @@ https.interceptors.response.use(
|
||||
if (tokenValue && config.header) config.header.Authorization = tokenValue
|
||||
return https.request(config)
|
||||
}
|
||||
clearBizSession()
|
||||
} catch (err) {
|
||||
clearBizSession()
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
@@ -148,21 +207,44 @@ https.interceptors.response.use(
|
||||
},
|
||||
)
|
||||
|
||||
// 从tokenStorage中读取tokenValue值
|
||||
// 从tokenStorage中读取tokenValue值(兼容嵌套 tokenValue / 诗画字符串 accessToken)
|
||||
const getTokenValue = (tokenStorage: any): string | null => {
|
||||
if (isDevEnv()) {
|
||||
return tokenStorage?.access_token ? `Bearer ${tokenStorage.access_token}` : null
|
||||
if (!tokenStorage || typeof tokenStorage !== "object") return null
|
||||
if (isDeviceVerifyMarker(tokenStorage)) return null
|
||||
const pick = (v: unknown): string | null => {
|
||||
if (typeof v === "string" && v.trim()) return v.trim()
|
||||
if (v && typeof v === "object" && typeof (v as { tokenValue?: unknown }).tokenValue === "string") {
|
||||
const tv = (v as { tokenValue: string }).tokenValue.trim()
|
||||
return tv || null
|
||||
}
|
||||
return null
|
||||
}
|
||||
return tokenStorage?.accessToken?.tokenValue ? `Bearer ${tokenStorage.accessToken.tokenValue}` : null
|
||||
const raw =
|
||||
pick(tokenStorage.accessToken) ||
|
||||
pick(tokenStorage.access_token) ||
|
||||
(typeof tokenStorage.tokenValue === "string" ? tokenStorage.tokenValue.trim() : null)
|
||||
return raw ? `Bearer ${raw}` : null
|
||||
}
|
||||
|
||||
// 检查tokenStorage中tokenValue是否过期
|
||||
// 检查是否过期;无有效 token / 脏 expiresAt 一律视为过期,避免半残数据永远「未过期」
|
||||
const isTokenExpires = (tokenStorage: any): boolean => {
|
||||
const { accessToken } = tokenStorage // 线上token字段
|
||||
const { access_token } = tokenStorage // 测试token字段
|
||||
|
||||
const expiresDate = new Date(accessToken?.expiresAt || access_token?.expiresAt).getTime()
|
||||
return new Date().getTime() >= expiresDate
|
||||
if (!tokenStorage || typeof tokenStorage !== "object") return true
|
||||
if (isDeviceVerifyMarker(tokenStorage)) return true
|
||||
if (!getTokenValue(tokenStorage)) return true
|
||||
const expiresAt =
|
||||
(tokenStorage.accessToken && typeof tokenStorage.accessToken === "object"
|
||||
? tokenStorage.accessToken.expiresAt
|
||||
: null) ??
|
||||
(tokenStorage.access_token && typeof tokenStorage.access_token === "object"
|
||||
? tokenStorage.access_token.expiresAt
|
||||
: null)
|
||||
if (expiresAt == null || expiresAt === "") {
|
||||
// 有 token 但无过期字段(部分诗画落库):先用,等 424 再刷
|
||||
return false
|
||||
}
|
||||
const expiresDate = new Date(expiresAt).getTime()
|
||||
if (Number.isNaN(expiresDate)) return true
|
||||
return Date.now() >= expiresDate
|
||||
}
|
||||
|
||||
async function refreshToken(): Promise<any> {
|
||||
@@ -211,16 +293,21 @@ async function refreshToken(): Promise<any> {
|
||||
return refreshTokenPromise
|
||||
}
|
||||
|
||||
// 根据应用环境自动登录 返回登录结果
|
||||
// 根据应用环境自动登录 返回登录结果(全局互斥,避免清缓存后并发打多遍单点)
|
||||
const autoLogin = async (): Promise<boolean> => {
|
||||
const appInfo = await getAppInfo()
|
||||
// 浙政钉环境
|
||||
if (appInfo == APPENV.ZZD) return await loginByZzd()
|
||||
// 诗画环境
|
||||
if (appInfo == APPENV.SH) return await loginBySh()
|
||||
|
||||
uni.showToast({ title: "app环境错误", icon: "none" })
|
||||
return false
|
||||
if (autoLoginPromise) return autoLoginPromise
|
||||
autoLoginPromise = (async () => {
|
||||
try {
|
||||
const appInfo = await getAppInfo()
|
||||
if (appInfo == APPENV.ZZD) return await loginByZzd()
|
||||
if (appInfo == APPENV.SH) return await loginBySh()
|
||||
uni.showToast({ title: "app环境错误", icon: "none" })
|
||||
return false
|
||||
} finally {
|
||||
autoLoginPromise = null
|
||||
}
|
||||
})()
|
||||
return autoLoginPromise
|
||||
}
|
||||
|
||||
function isZzdNeedSmsVerify(data: unknown): data is { needSmsVerify: true; bindSessionId: string; displayName?: string } {
|
||||
@@ -244,75 +331,87 @@ function goZzdSmsVerify(data: { bindSessionId: string; displayName?: string }) {
|
||||
* @returns ok 已可带 token 请求;sms 已跳转核验页;failed 未登录
|
||||
*/
|
||||
async function zzdLoginCore(): Promise<"ok" | "sms" | "failed"> {
|
||||
try {
|
||||
uni.showLoading({ title: "登录中...", mask: true })
|
||||
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
|
||||
if (!isEmpty(tokenStorage) && !isTokenExpires(tokenStorage) && getTokenValue(tokenStorage)) {
|
||||
uni.hideLoading()
|
||||
return "ok"
|
||||
}
|
||||
if (zzdLoginPromise) return zzdLoginPromise
|
||||
zzdLoginPromise = (async () => {
|
||||
try {
|
||||
uni.showLoading({ title: "登录中...", mask: true })
|
||||
sanitizeBizSession()
|
||||
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
|
||||
if (hasUsableBizToken(tokenStorage)) {
|
||||
return "ok"
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
const { requestZzdAuthCodeViaGdt } = await import("./dingAuthCode")
|
||||
const corpId = (import.meta.env.VITE_ZZD_CORP_ID || "").trim()
|
||||
const authCode = await requestZzdAuthCodeViaGdt(corpId)
|
||||
const res: any = await userApi.auth({ authCode })
|
||||
if (res?.code == 0) {
|
||||
if (isZzdNeedSmsVerify(res.data)) {
|
||||
goZzdSmsVerify(res.data as { bindSessionId: string; displayName?: string })
|
||||
return "sms"
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
const { requestZzdAuthCodeViaGdt } = await import("./dingAuthCode")
|
||||
const corpId = (import.meta.env.VITE_ZZD_CORP_ID || "").trim()
|
||||
const authCode = await requestZzdAuthCodeViaGdt(corpId)
|
||||
const res: any = await userApi.auth({ authCode })
|
||||
if (res?.code == 0) {
|
||||
if (isZzdNeedSmsVerify(res.data)) {
|
||||
goZzdSmsVerify(res.data as { bindSessionId: string; displayName?: string })
|
||||
return "sms"
|
||||
}
|
||||
const d = res.data
|
||||
if (!d || typeof d !== "object" || (!d.accessToken && !d.refreshToken)) {
|
||||
clearBizSession()
|
||||
uni.showToast({ title: "登录返回数据异常,请重试", icon: "none", duration: 4000 })
|
||||
return "failed"
|
||||
}
|
||||
uni.setStorageSync(TOKEN_KEY, res.data)
|
||||
const { data: userData } = await userApi.userInfo()
|
||||
uni.setStorageSync(USER_INFO_KEY, userData)
|
||||
const { onZzdLoginSuccessTrack } = await import("./zzd-aplus-track")
|
||||
void onZzdLoginSuccessTrack()
|
||||
return "ok"
|
||||
}
|
||||
const d = res.data
|
||||
if (!d || typeof d !== "object" || (!d.accessToken && !d.refreshToken)) {
|
||||
uni.showToast({ title: "登录返回数据异常,请重试", icon: "none", duration: 4000 })
|
||||
return "failed"
|
||||
clearBizSession()
|
||||
if (res?.msg) {
|
||||
uni.showToast({ title: res.msg, icon: "none", duration: 4000 })
|
||||
}
|
||||
uni.setStorageSync(TOKEN_KEY, res.data)
|
||||
const { data: userData } = await userApi.userInfo()
|
||||
uni.setStorageSync(USER_INFO_KEY, userData)
|
||||
const { onZzdLoginSuccessTrack } = await import("./zzd-aplus-track")
|
||||
void onZzdLoginSuccessTrack()
|
||||
return "ok"
|
||||
return "failed"
|
||||
} catch (e) {
|
||||
console.warn("[loginByZzd] authCode 登录未成功,尝试 uabaseToken", e)
|
||||
}
|
||||
if (res?.msg) {
|
||||
uni.showToast({ title: res.msg, icon: "none", duration: 4000 })
|
||||
}
|
||||
|
||||
const token = typeof window !== "undefined" ? getUrlQueryParam("uabaseToken") || null : null
|
||||
|
||||
if (!token) {
|
||||
clearBizSession()
|
||||
uni.showToast({ title: "用户凭证已失效,请重新进入智慧后勤应用", icon: "none", duration: 6000 })
|
||||
return "failed"
|
||||
}
|
||||
|
||||
const res = await userApi.auth({ token })
|
||||
if (res?.code == 0) {
|
||||
uni.setStorageSync(TOKEN_KEY, res.data)
|
||||
const { data: userData } = await userApi.userInfo()
|
||||
uni.setStorageSync(USER_INFO_KEY, userData)
|
||||
const { onZzdLoginSuccessTrack } = await import("./zzd-aplus-track")
|
||||
void onZzdLoginSuccessTrack()
|
||||
return "ok"
|
||||
} else {
|
||||
clearBizSession()
|
||||
if (res.msg == "token已失效") {
|
||||
uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 })
|
||||
}
|
||||
return "failed"
|
||||
} catch (e) {
|
||||
console.warn("[loginByZzd] authCode 登录未成功,尝试 uabaseToken", e)
|
||||
}
|
||||
}
|
||||
|
||||
const token = typeof window !== "undefined" ? getUrlQueryParam("uabaseToken") || null : null
|
||||
|
||||
if (!token) {
|
||||
uni.showToast({ title: "用户凭证已失效,请重新进入智慧后勤应用", icon: "none", duration: 6000 })
|
||||
} catch (error: any) {
|
||||
clearBizSession()
|
||||
uni.showToast({ title: error, icon: "none" })
|
||||
return "failed"
|
||||
}
|
||||
|
||||
const res = await userApi.auth({ token })
|
||||
if (res?.code == 0) {
|
||||
uni.setStorageSync(TOKEN_KEY, res.data)
|
||||
const { data: userData } = await userApi.userInfo()
|
||||
uni.setStorageSync(USER_INFO_KEY, userData)
|
||||
const { onZzdLoginSuccessTrack } = await import("./zzd-aplus-track")
|
||||
void onZzdLoginSuccessTrack()
|
||||
return "ok"
|
||||
} else {
|
||||
uni.removeStorageSync(TOKEN_KEY)
|
||||
uni.removeStorageSync(USER_INFO_KEY)
|
||||
if (res.msg == "token已失效") {
|
||||
uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 })
|
||||
} finally {
|
||||
try {
|
||||
uni.hideLoading()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return "failed"
|
||||
zzdLoginPromise = null
|
||||
}
|
||||
} catch (error: any) {
|
||||
uni.showToast({ title: error, icon: "none" })
|
||||
return "failed"
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
})()
|
||||
return zzdLoginPromise
|
||||
}
|
||||
|
||||
async function loginByZzd(): Promise<boolean> {
|
||||
@@ -324,54 +423,74 @@ async function loginByZzd(): Promise<boolean> {
|
||||
* 从门户/常用入口进入 H5 时主动跑浙政钉单点。非浙政钉环境立即返回 ok,不调用 zzdsso、不碰诗画逻辑。
|
||||
*/
|
||||
export async function tryZzdEntryLogin(): Promise<"ok" | "sms" | "failed"> {
|
||||
sanitizeBizSession()
|
||||
const appInfo = await getAppInfo()
|
||||
if (appInfo !== APPENV.ZZD) return "ok"
|
||||
return await zzdLoginCore()
|
||||
}
|
||||
|
||||
// 诗画登录
|
||||
// 诗画登录(互斥:避免「我的」与请求拦截器各打一遍 /shjnsso)
|
||||
async function loginBySh(): Promise<boolean> {
|
||||
try {
|
||||
uni.showLoading({ title: "登录中...", mask: true })
|
||||
if (shLoginPromise) return shLoginPromise
|
||||
shLoginPromise = (async () => {
|
||||
try {
|
||||
uni.showLoading({ title: "登录中...", mask: true })
|
||||
sanitizeBizSession()
|
||||
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
|
||||
if (hasUsableBizToken(tokenStorage)) {
|
||||
return true
|
||||
}
|
||||
|
||||
const userInfo = await (window as any).CP2.getUserInfo()
|
||||
if (userInfo.code == 0 || !userInfo.data.accountId || !userInfo.data.sessionId) {
|
||||
uni.showToast({ title: "获取诗画用户信息失败", icon: "none" })
|
||||
return false
|
||||
}
|
||||
|
||||
const { accountId, sessionId } = userInfo.data
|
||||
const response = await userApi.authsh({ accountId, sessionId })
|
||||
// const response = await userApi.authsh({ accountId: "67e790d6a95d5a7b507e53dd", sessionId: "68215b03a95d5a4899d20cb4" }) // 测试
|
||||
|
||||
if (response.code == 0) {
|
||||
uni.setStorageSync(TOKEN_KEY, { accessToken: response.data.accessToken })
|
||||
const { data: userData } = await userApi.userInfo()
|
||||
uni.setStorageSync(USER_INFO_KEY, userData)
|
||||
uni.showToast({ title: "登录成功", icon: "none" })
|
||||
return true
|
||||
} else {
|
||||
uni.removeStorageSync(TOKEN_KEY)
|
||||
uni.removeStorageSync(USER_INFO_KEY)
|
||||
if (response.msg == "token已失效") {
|
||||
uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 })
|
||||
const userInfo = await (window as any).CP2.getUserInfo()
|
||||
if (userInfo.code == 0 || !userInfo.data.accountId || !userInfo.data.sessionId) {
|
||||
uni.showToast({ title: "获取诗画用户信息失败", icon: "none" })
|
||||
return false
|
||||
}
|
||||
|
||||
// 设备未信任:后端 R.failed(-1, msg) 业务码在 code,data 为 null
|
||||
if (response.code == -1) {
|
||||
uni.setStorageSync(TOKEN_KEY, { data: -1 })
|
||||
}
|
||||
const { accountId, sessionId } = userInfo.data
|
||||
const response = await userApi.authsh({ accountId, sessionId })
|
||||
|
||||
uni.showToast({ title: response.msg || "登录失败", icon: "none" })
|
||||
if (response.code == 0) {
|
||||
const at = response.data?.accessToken
|
||||
// 统一成可被 getTokenValue 识别的结构,避免只落了字符串导致后续读不到
|
||||
if (typeof at === "string") {
|
||||
uni.setStorageSync(TOKEN_KEY, { accessToken: { tokenValue: at } })
|
||||
} else {
|
||||
uni.setStorageSync(TOKEN_KEY, { accessToken: at })
|
||||
}
|
||||
const { data: userData } = await userApi.userInfo()
|
||||
uni.setStorageSync(USER_INFO_KEY, userData)
|
||||
uni.showToast({ title: "登录成功", icon: "none" })
|
||||
return true
|
||||
} else {
|
||||
clearBizSession()
|
||||
if (response.msg == "token已失效") {
|
||||
uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 })
|
||||
return false
|
||||
}
|
||||
|
||||
// 设备未信任:后端 R.failed(-1, msg) 业务码在 code,data 为 null
|
||||
if (response.code == -1) {
|
||||
uni.setStorageSync(TOKEN_KEY, { data: -1 })
|
||||
}
|
||||
|
||||
uni.showToast({ title: response.msg || "登录失败", icon: "none" })
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
clearBizSession()
|
||||
uni.showToast({ title: "登录错误", icon: "none" })
|
||||
return false
|
||||
} finally {
|
||||
try {
|
||||
uni.hideLoading()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
shLoginPromise = null
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({ title: "登录错误", icon: "none" })
|
||||
return false
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
})()
|
||||
return shLoginPromise
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,13 +498,28 @@ async function loginBySh(): Promise<boolean> {
|
||||
* 与「我的」里先登录再扫码一致,保证后续支付接口带用户身份。
|
||||
*/
|
||||
export async function ensureBizLogin(): Promise<boolean> {
|
||||
sanitizeBizSession()
|
||||
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
|
||||
if (!isEmpty(tokenStorage) && !isTokenExpires(tokenStorage) && getTokenValue(tokenStorage)) {
|
||||
if (hasUsableBizToken(tokenStorage)) {
|
||||
return true
|
||||
}
|
||||
return await autoLogin()
|
||||
}
|
||||
|
||||
/**
|
||||
* 进不去页时的「清缓存重试」:清 token/用户/诗画角色后重新自动登录。
|
||||
* 保留 URL 上的 launchParam。
|
||||
*/
|
||||
export async function recoverBizSession(): Promise<boolean> {
|
||||
clearBizSession({ clearRole: true })
|
||||
zzdLoginPromise = null
|
||||
shLoginPromise = null
|
||||
autoLoginPromise = null
|
||||
refreshTokenPromise = null
|
||||
isRefreshing = false
|
||||
return await autoLogin()
|
||||
}
|
||||
|
||||
// 请求函数
|
||||
const request = (config: any) => {
|
||||
return https.middleware(config)
|
||||
|
||||
Reference in new issue
Block a user