This commit is contained in:
dzc committed 2026-08-20 19:43:56 +08:00
commit e1ac23b5af
8 files changed
+372 -182

No files matched your search

+2 -1
View File
@@ -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()
+13 -2
View File
@@ -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
View File
@@ -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:浙政钉登录
+38 -6
View File
@@ -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(() => {
+18 -9
View File
@@ -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
}
+9 -4
View File
@@ -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)
+11 -1
View File
@@ -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
View File
@@ -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)