This commit is contained in:
dzc committed 2026-05-11 20:09:06 +08:00
commit b2d022ce5f
16 files changed
+520 -35

No files matched your search

+11 -4
View File
@@ -1,3 +1,7 @@
# H5:true 时在页面加载 vConsole(浙政钉内可看 Network/Log);稳定上线后改为 false
#VITE_VCONSOLE=true
VITE_VCONSOLE=false
#是否是TOC应用
VITE_IS_TOC=true
@@ -16,18 +20,21 @@ VITE_PWD_ENC_KEY='pigxpigxpigxpigx'
# OAUTH2 APP模式客户端信息
VITE_OAUTH2_APP_CLIENT='mini:mini'
# 浙政钉 H5:getAuthCode 用 corpId(与 zzdhome 一致,浙政钉内打开业务 H5 时必填)
# VITE_ZZD_CORP_ID=
# --------------------------- 开发环境 api地址 ----------------------------------
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
; VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
# VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
# --------------------------- 开发环境 图片访问基础地址 ---------------------------
# VITE_IMAGE_DEV_PATH = https://stgl.pj.gov.cn/api # 域名线上
#VITE_IMAGE_DEV_PATH = http://59.202.170.152:81/api # 信创线上
VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc # 邓-本地
#VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
#VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc # 邓-本地
VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
# VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
# --------------------------- 生产环境 api地址 ----------------------------------
-5
View File
@@ -4,7 +4,6 @@
<meta charset="UTF-8" />
<script src="https://dapp.tmuyun.com/oss-static/app/jcommind.js"></script>
<script src="https://g.alicdn.com/gdt/jsapi/1.9.43/index.js"></script>
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script> -->
<script>
var coverSupport =
"CSS" in window && typeof CSS.supports === "function" && (CSS.supports("top: env(a)") || CSS.supports("top: constant(a)"))
@@ -21,9 +20,5 @@
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
<!-- <script>
var vConsole = new VConsole()
console.log("console:", vConsole)
</script> -->
</body>
</html>
+4 -1
View File
@@ -43,6 +43,7 @@
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"@babel/runtime": "^7.26.0",
"@dcloudio/uni-app": "3.0.0-4020920240930001",
"@dcloudio/uni-app-harmony": "3.0.0-4020920240930001",
"@dcloudio/uni-app-plus": "3.0.0-4020920240930001",
@@ -62,11 +63,13 @@
"@zebra-ui/swiper": "^2.2.8",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"gdt-jsapi": "^1.9.55-beta.14",
"luch-request": "^3.1.1",
"pinia": "^2.2.6",
"qrcode": "^1.5.4",
"qs": "^6.13.1",
"radash": "^12.1.0",
"vconsole": "^3.15.1",
"vue": "^3.3.11",
"vue-i18n": "^9.1.9",
"vue-waterfall-plugin-next": "^2.6.5",
@@ -90,4 +93,4 @@
"vite": "5.2.8",
"vue-tsc": "^1.0.24"
}
}
}
+13 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { getLaunchParam } from "@/sheep/utils/app"
import { getLaunchParam, getUrlQueryParam } from "@/sheep/utils/app"
import { getAppInfo } from "@@/utils/app"
import { APPENV } from "./sheep/constant"
@@ -164,6 +164,18 @@ const setTabbar = (launchParam?: string, auth?: string) => {
}
export default {
async onLaunch() {
try {
const lp = getUrlQueryParam("launchParam")
if (lp) {
uni.setStorageSync("launchParam", lp)
}
const { tryZzdEntryLogin } = await import("@/sheep/utils/request")
await tryZzdEntryLogin()
} catch (e) {
console.warn("[App.onLaunch] tryZzdEntryLogin", e)
}
},
methods: {
async _initTabbar() {
const launchParam = getLaunchParam()
+13
View File
@@ -1,5 +1,18 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_VCONSOLE?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
declare module "gdt-jsapi" {
const dd: { getAuthCode: (opts: { corpId?: string }) => Promise<unknown> }
export default dd
}
declare module "*.vue" {
import { DefineComponent } from "vue"
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+7
View File
@@ -5,6 +5,13 @@ import pinia from "../src/sheep/stores/index"
import App from "./App.vue"
import tmUi from "./uni_modules/tm-ui"
// #ifdef H5
import VConsole from "vconsole"
if (import.meta.env.VITE_VCONSOLE === "true") {
new VConsole()
}
// #endif
export function createApp() {
const app = createSSRApp(App)
+7
View File
@@ -383,6 +383,13 @@
"navigationBarTitleText": "用户身份",
"disableScroll": true
}
},
{
"path": "zzdSmsVerify",
"style": {
"navigationBarTitleText": "手机号核验",
"disableScroll": true
}
}
]
},
+17 -3
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { APPENV } from "@/sheep/constant"
import { getAppInfo, getLaunchParam, isDevEnv } from "@/sheep/utils/app"
import { getAppInfo, getLaunchParam, getUrlQueryParam, isDevEnv } from "@/sheep/utils/app"
import { onShow } from "@dcloudio/uni-app"
const moduleList = [
@@ -61,7 +61,10 @@ const moduleList = [
]
onShow(() => {
uni.removeStorageSync("launchParam")
const fromUrl = getUrlQueryParam("launchParam")
if (fromUrl) {
uni.setStorageSync("launchParam", fromUrl)
}
jumpByParam()
})
@@ -69,9 +72,15 @@ const jumpByParam = async () => {
const appInfo = await getAppInfo() // 环境判断
const launchParam = getLaunchParam() // 获取启动参数
// 浙政钉环境
// 浙政钉环境(诗画不走此分支)
if (appInfo == APPENV.ZZD) {
const { tryZzdEntryLogin } = await import("@/sheep/utils/request")
const zzdR = await tryZzdEntryLogin()
if (zzdR !== "ok") return
switch (launchParam) {
case "thirdApp":
uni.setStorageSync("launchParam", launchParam)
return uni.reLaunch({ url: "/pages/thirdApp/happyMall" })
case "canteen": // 食堂
uni.setStorageSync("launchParam", launchParam)
uni.switchTab({ url: "/pages/tabbar/canteen" })
@@ -101,6 +110,11 @@ const jumpByParam = async () => {
uni.switchTab({ url: "/pages/tabbar/prosperityShopMchList" })
break
default:
if (!launchParam) {
uni.showToast({ title: "缺少入口参数,请从工作台重新进入", icon: "none", duration: 3500 })
} else {
uni.showToast({ title: "暂不支持的入口:" + launchParam, icon: "none", duration: 3500 })
}
uni.hideTabBar()
break
}
+14 -5
View File
@@ -1,8 +1,8 @@
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<template #main-top>
<view class="flex items-center py-3 px-5" @tap="doUserArea">
<view class="flex items-center" v-if="state.userInfo?.sysUser?.userId">
<view class="flex items-center py-3 px-5">
<view class="flex items-center" v-if="state.userInfo?.sysUser?.userId" @tap="doUserArea">
<image v-if="state.userInfo?.sysUser?.avatar" class="size-100rpx rounded-full"
:src="filePath(state.userInfo.sysUser.avatar)" mode="aspectFill" />
<image v-else class="w-100rpx h-100rpx rounded-full" src="/static/icon/default-avatar.png"
@@ -115,6 +115,7 @@ const STORAGE_KEYS = {
}
const doUserArea = () => {
if (!state.userInfo?.sysUser?.userId) return
modifyUserRef.value?.open()
}
@@ -158,7 +159,7 @@ const storage = {
if (res.confirm) {
uni.clearStorageSync()
state.userInfo = {}
uni.reLaunch({ url: "/pages/tabbar/my" })
uni.reLaunch({ url: "/pages/tabbar/my?reauth=1" })
}
},
})
@@ -233,6 +234,9 @@ const login = {
if (response.msg == "token已失效") {
uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 })
} else {
if (response.code == -1) {
storage.saveToken({ data: -1 })
}
uni.showToast({ title: response.msg || "登录失败", icon: "none" })
}
}
@@ -242,7 +246,7 @@ const login = {
if (isEmpty(tokenStorage) || isEmpty(userStorage)) {
try {
uni.showLoading({ title: "登录中...", mask: true })
const res = await userApi.auth(token)
const res = await userApi.auth({ token })
if (res.code == 0) {
storage.saveToken(res.data)
@@ -376,7 +380,7 @@ const initMenuConfig = () => {
}
}
onLoad(async () => {
onLoad(async (options: Record<string, string | undefined>) => {
const app = getApp()
app._initTabbar()
@@ -393,6 +397,11 @@ onLoad(async () => {
// 获取存储中的用户信息
storage.getUserInfo()
if (options?.reauth === "1") {
await login.checkAppInfo()
storage.getUserInfo()
}
})
const isMallMchUser = async () => {
+119
View File
@@ -0,0 +1,119 @@
<script setup lang="ts">
import { ref } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { constant } from "@@/index"
import { userApi } from "@@/api"
/** 与后端 ThirdPartyZzdBizCodes.SYS_USER_NOT_FOUND_FOR_ZZD_BIND 一致 */
const CODE_ZZD_SYS_USER_NOT_FOUND = 46001
const bindSessionId = ref("")
const displayName = ref("")
const mobile = ref("")
const smsCode = ref("")
onLoad((q: Record<string, string | undefined>) => {
bindSessionId.value = decodeURIComponent(q.bindSessionId || "")
displayName.value = decodeURIComponent(q.displayName || "")
})
async function sendSms() {
const m = mobile.value.trim()
if (!m) {
uni.showToast({ title: "请输入手机号", icon: "none" })
return
}
try {
const r: any = await userApi.zzdLoginSms(m)
if (r?.code !== 0) {
uni.showToast({ title: r?.msg || "发送失败", icon: "none" })
return
}
uni.showToast({ title: "验证码已发送", icon: "none" })
} catch (e: any) {
uni.showToast({ title: e?.message || "发送失败", icon: "none" })
}
}
async function confirm() {
const m = mobile.value.trim()
const code = smsCode.value.trim()
if (!bindSessionId.value || !m || !code) {
uni.showToast({ title: "请填写手机号与验证码", icon: "none" })
return
}
uni.showLoading({ title: "核验中...", mask: true })
try {
const r: any = await userApi.zzdSsoConfirmPhone({
bindSessionId: bindSessionId.value,
mobilePhone: m,
smsCode: code,
})
if (r?.code !== 0) {
if (r?.code === CODE_ZZD_SYS_USER_NOT_FOUND) {
uni.showModal({
title: "暂无系统账号",
content:
r?.msg ||
"本系统暂无该手机号对应的用户,请联系管理员申请开通账号后,再使用本机号码完成浙政钉绑定登录。",
showCancel: false,
})
} else {
uni.showToast({ title: r?.msg || "核验失败", icon: "none" })
}
return
}
const tokenKey = constant.storageConstant.TOKEN
const userInfoKey = constant.storageConstant.USERINFO
uni.setStorageSync(tokenKey, r.data)
const { data: userData } = await userApi.userInfo()
uni.setStorageSync(userInfoKey, userData)
uni.showToast({ title: "登录成功", icon: "none" })
setTimeout(() => {
uni.reLaunch({ url: "/pages/tabbar/index" })
}, 400)
} catch (e: any) {
uni.showToast({ title: e?.message || "核验失败", icon: "none" })
} finally {
uni.hideLoading()
}
}
</script>
<template>
<crud-layout title="手机号核验" bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<view class="card p-x-30rpx p-y-40rpx">
<view v-if="displayName" class="m-b-24rpx text-28rpx color-#666">浙政钉用户:{{ displayName }}</view>
<tm-form direction="vertical">
<tm-form-item label="手机号" :showBottom="false">
<tm-input v-model="mobile" type="number" placeholder="请输入手机号" />
</tm-form-item>
<view class="h-30rpx" />
<tm-form-item label="短信验证码" :showBottom="false">
<tm-input v-model="smsCode" type="number" placeholder="验证码" />
</tm-form-item>
</tm-form>
</view>
<view
class="bg-#f0f5ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-40rpx sub-text-30 color-#0958d9!"
@tap="sendSms"
>
获取验证码
</view>
<view
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-30rpx sub-text-30 color-white!"
@tap="confirm"
>
核验并进入应用
</view>
</crud-layout>
</template>
<style lang="scss" scoped>
:deep() {
.tmFormItemLabel {
padding-bottom: 10rpx !important;
}
}
</style>
+16 -3
View File
@@ -12,12 +12,25 @@ export default {
method: "GET",
}),
// 浙政钉授权登录
auth: (token: string): Promise<any> =>
// 浙政钉授权登录(token:旧 uabaseToken;authCode:开放平台 H5 getAuthCode)
auth: (payload: { token?: string; authCode?: string }): Promise<any> =>
request({
url: "/thirdparty/thirdPartyOAth2/zzdsso",
method: "POST",
data: { token },
data: payload,
}),
zzdLoginSms: (mobilePhone: string): Promise<any> =>
request({
url: `/thirdparty/thirdPartyOAth2/loginSms?mobilePhone=${encodeURIComponent(mobilePhone.trim())}`,
method: "POST",
}),
zzdSsoConfirmPhone: (data: { bindSessionId: string; mobilePhone: string; smsCode: string }): Promise<any> =>
request({
url: "/thirdparty/thirdPartyOAth2/zzdsso/confirmPhone",
method: "POST",
data,
}),
// 诗画授权登录
+19 -1
View File
@@ -5,13 +5,31 @@ import { APPENV } from "../constant"
// export default () => import.meta.env.VITE_DEV_ENV === "dev"
export const isDevEnv = (): boolean => process.env.NODE_ENV === "development"
/**
* 读取当前页 URL 查询参数(H5)。兼容 hash 路由:`/h5/#/?launchParam=canteen` 中参数在 hash 段,不在 location.search。
*/
export const getUrlQueryParam = (name: string): string => {
if (typeof window === "undefined") return ""
const u = new URL(window.location.href)
const fromSearch = u.searchParams.get(name)
if (fromSearch) return fromSearch
const hash = window.location.hash || ""
const qi = hash.indexOf("?")
if (qi < 0) return ""
const fromHash = new URLSearchParams(hash.slice(qi + 1)).get(name)
return fromHash || ""
}
// 获取启动参数
export const getLaunchParam = (): string => {
return new URL(window.location.href).searchParams.get("launchParam") || uni.getStorageSync("launchParam") || ""
return getUrlQueryParam("launchParam") || uni.getStorageSync("launchParam") || ""
}
// 检测app信息判断运行环境
export const getAppInfo = async (): Promise<string> => {
if (import.meta.env.VITE_DEV_FORCE_ZZD === "true") {
return APPENV.ZZD
}
try {
const res = await (window as any).CP2.getAppInfo()
// 浙政钉环境
+188
View File
@@ -0,0 +1,188 @@
import gdtJsapi from "gdt-jsapi"
import { loadScript } from "./loadScript"
const JSAPI_LOAD_TIMEOUT_MS = 45000
/** 仅在不注入 dd 的环境(如普通浏览器)按需拉取;浙政钉内通常自带 window.dd */
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
let ddErrorCallbackRegistered = false
declare global {
interface Window {
dd?: {
env?: { platform?: string }
/** 政务钉钉 / 专有钉钉:JSAPI 鉴权(ticket 来自开放平台 get_jsapi_token) */
authConfig?: (opts: { ticket: string; jsApiList: string[] }) => Promise<unknown>
config?: (opts: {
agentId: string
corpId: string
timeStamp: string
nonceStr: string
signature: string
type?: number
jsApiList: string[]
}) => void
/** 专有钉钉 / 浙政钉文档:获取登录授权码(Promise),移动端返回 code,PC 返回 auth_code */
getAuthCode?: (opts: { corpId?: string }) => Promise<{
code?: string
auth_code?: string
expiresln?: number
expires_in?: number
}>
ready: (cb: () => void) => void
/** 鉴权/配置失败时回调,便于排查未触发取码的原因 */
error?: (cb: (err: unknown) => void) => void
runtime: {
permission: {
requestAuthCode: (opts: {
corpId: string
onSuccess: (info: { code: string }) => void
onFail?: (err: unknown) => void
}) => void
}
}
}
}
}
function registerDdErrorOnce() {
const dd = window.dd
if (!dd || ddErrorCallbackRegistered || typeof dd.error !== "function") return
ddErrorCallbackRegistered = true
try {
dd.error((err: unknown) => {
console.error("[dd.error]", typeof err === "string" ? err : err)
})
} catch {
/* ignore */
}
}
function injectWaitMs(): number {
const v = import.meta.env.VITE_DD_INJECT_WAIT_MS
if (v === undefined || v === "") return 5000
const n = parseInt(String(v), 10)
return Number.isFinite(n) && n >= 0 ? n : 5000
}
/** 部分 IRS/扫码场景下容器晚于首屏注入 dd,短轮询避免误判 */
function waitForNativeDd(maxMs: number): Promise<boolean> {
if (maxMs <= 0) return Promise.resolve(!!window.dd)
if (window.dd) return Promise.resolve(true)
return new Promise((resolve) => {
const t0 = Date.now()
const id = setInterval(() => {
if (window.dd) {
clearInterval(id)
resolve(true)
} else if (Date.now() - t0 >= maxMs) {
clearInterval(id)
resolve(false)
}
}, 80)
})
}
/**
* 浙政钉 / 钉钉容器内需在 dd.ready 之后再调 getAuthCode / requestAuthCode,否则 Android 上常见无回调。
*/
export function ensureDdReady(): Promise<void> {
return new Promise((resolve, reject) => {
const dd = window.dd
if (!dd) {
reject(new Error("window.dd 不存在"))
return
}
if (typeof dd.ready !== "function") {
resolve()
return
}
let settled = false
const to = setTimeout(() => {
if (settled) return
settled = true
const msg = `dd.ready 超时(${DD_READY_TIMEOUT_MS / 1000}s)。完整单点需先 dd.authConfig;或调试时跳过 ready 直接 getAuthCode。`
reject(new Error(msg))
}, DD_READY_TIMEOUT_MS)
try {
dd.ready(() => {
if (settled) return
settled = true
clearTimeout(to)
resolve()
})
} catch (e) {
clearTimeout(to)
if (!settled) {
settled = true
reject(e instanceof Error ? e : new Error(String(e)))
}
}
})
}
/**
* 1) 优先用容器自带的 `window.dd`(浙政钉内嵌常见)。
* 2) 若首屏没有 dd:先等待 `VITE_DD_INJECT_WAIT_MS`(默认 5s)以兼容延迟注入。
* 3) 仍没有时:默认自动拉 g.alicdn open.js(IRS 测试、扫码开系统浏览器等);纯内嵌且绝不拉脚本可设 `VITE_DD_NO_OPEN_JS_FALLBACK=true`。
* 4) 显式 `VITE_DD_LOAD_OPEN_JS=true` 时跳过等待立即拉脚本(本地 Chrome 调试可配合 .env.development)。
*/
export async function loadDingTalkOpenJsapi() {
if (window.dd) {
registerDdErrorOnce()
return
}
const forceScript = import.meta.env.VITE_DD_LOAD_OPEN_JS === "true"
const noFallback = import.meta.env.VITE_DD_NO_OPEN_JS_FALLBACK === "true"
if (!forceScript) {
const waitMs = injectWaitMs()
if (waitMs > 0) {
const ok = await waitForNativeDd(waitMs)
if (ok) {
registerDdErrorOnce()
return
}
}
if (noFallback) {
const msg =
"等待后仍无 window.dd。请确认链接在浙政钉内置 WebView 打开;或去掉 VITE_DD_NO_OPEN_JS_FALLBACK、或设 VITE_DD_LOAD_OPEN_JS=true"
throw new Error(msg)
}
}
const scriptUrl = (import.meta.env.VITE_DD_JSAPI_URL || "").trim() || DEFAULT_OPEN_JS_URL
await Promise.race([
loadScript(scriptUrl),
new Promise<never>((_, rej) =>
setTimeout(
() => rej(new Error(`钉钉 JSAPI 脚本加载超时(${JSAPI_LOAD_TIMEOUT_MS / 1000}s)`)),
JSAPI_LOAD_TIMEOUT_MS,
),
),
])
if (!window.dd) {
throw new Error("脚本已执行但 window.dd 仍不存在")
}
registerDdErrorOnce()
}
/** gdt-jsapi 容器返回:code / auth_code / authCode(与 zzdhome `src/App.vue` 中 `authCodeFromGetAuthResult` 一致) */
export function authCodeFromGdtResult(res: unknown): string | null {
if (!res || typeof res !== "object") return null
const o = res as Record<string, unknown>
const c = o.code ?? o.auth_code ?? o.authCode
if (typeof c === "string" && c.trim()) return c.trim()
return null
}
/**
* 与 zzdhome `App.vue` 的 `runZzdLogin` 一致:`loadDingTalkOpenJsapi` 后使用 `gdt-jsapi` 的 `getAuthCode`,不再单独用 window.dd 包一层 Promise。
*/
export async function requestZzdAuthCodeViaGdt(corpId: string): Promise<string> {
await loadDingTalkOpenJsapi()
const res = await gdtJsapi.getAuthCode({ corpId: (corpId || "").trim() })
const code = authCodeFromGdtResult(res)
if (!code) throw new Error("getAuthCode 未返回有效 code(请核对 gdt 返回字段)")
return code
}
+10
View File
@@ -0,0 +1,10 @@
export function loadScript(src: string): Promise<void> {
return new Promise((resolve, reject) => {
const el = document.createElement("script")
el.src = src
el.async = true
el.onload = () => resolve()
el.onerror = () => reject(new Error(`脚本加载失败: ${src}`))
document.head.appendChild(el)
})
}
+81 -11
View File
@@ -1,7 +1,7 @@
import Request from "luch-request"
import { router, constant } from "@@/index"
import { isEmpty } from "radash"
import { getAppInfo, isDevEnv } from "@@/utils/app"
import { getAppInfo, getUrlQueryParam, isDevEnv } from "@@/utils/app"
import { userApi } from "../api"
import showVerifyDeviceImpModel from "../components/verifyDeviceImpModel/verifyDeviceImpModel"
import { APPENV } from "../constant"
@@ -219,39 +219,109 @@ const autoLogin = async (): Promise<boolean> => {
return false
}
// 浙政钉登录
async function loginByZzd(): Promise<boolean> {
function isZzdNeedSmsVerify(data: unknown): data is { needSmsVerify: true; bindSessionId: string; displayName?: string } {
if (!data || typeof data !== "object") return false
const o = data as Record<string, unknown>
const need =
o.needSmsVerify === true || o.needSmsVerify === "true" || o.needSmsVerify === 1 || o.needSmsVerify === "1"
const sid = o.bindSessionId
const sidStr = typeof sid === "string" ? sid : sid != null ? String(sid) : ""
return need && sidStr.length > 0
}
function goZzdSmsVerify(data: { bindSessionId: string; displayName?: string }) {
const sid = typeof data.bindSessionId === "string" ? data.bindSessionId : String(data.bindSessionId || "")
const name = typeof data.displayName === "string" ? data.displayName : ""
const q = `bindSessionId=${encodeURIComponent(sid)}&displayName=${encodeURIComponent(name)}`
uni.reLaunch({ url: `/pages/user/zzdSmsVerify?${q}` })
}
/**
* 浙政钉专用:校验本地 token → 无效则 getAuthCode→zzdsso → 需绑手机则去短信页 → 否则换 token。
* @returns ok 已可带 token 请求;sms 已跳转核验页;failed 未登录
*/
async function zzdLoginCore(): Promise<"ok" | "sms" | "failed"> {
try {
uni.showLoading({ title: "登录中...", mask: true })
const token = new URL(window.location.href).searchParams.get("uabaseToken")
const tokenStorage = uni.getStorageSync(TOKEN_KEY)
if (!isEmpty(tokenStorage) && !isTokenExpires(tokenStorage) && getTokenValue(tokenStorage)) {
uni.hideLoading()
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"
}
const d = res.data
if (!d || typeof d !== "object" || (!d.accessToken && !d.refreshToken)) {
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)
return "ok"
}
if (res?.msg) {
uni.showToast({ title: res.msg, icon: "none", duration: 4000 })
}
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 })
return false
return "failed"
}
const res = await userApi.auth(token)
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)
return true
return "ok"
} else {
uni.removeStorageSync(TOKEN_KEY)
uni.removeStorageSync(USER_INFO_KEY)
if (res.msg == "token已失效") {
uni.showToast({ title: "用户凭证已失效,请退出并重新进入智慧后勤应用", icon: "none", duration: 6000 })
}
return false
return "failed"
}
} catch (error: any) {
uni.showToast({ title: error, icon: "none" })
return false
return "failed"
} finally {
uni.hideLoading()
}
}
async function loginByZzd(): Promise<boolean> {
const r = await zzdLoginCore()
return r === "ok"
}
/**
* 从门户/常用入口进入 H5 时主动跑浙政钉单点。非浙政钉环境立即返回 ok,不调用 zzdsso、不碰诗画逻辑。
*/
export async function tryZzdEntryLogin(): Promise<"ok" | "sms" | "failed"> {
const appInfo = await getAppInfo()
if (appInfo !== APPENV.ZZD) return "ok"
return await zzdLoginCore()
}
// 诗画登录
async function loginBySh(): Promise<boolean> {
try {
@@ -281,8 +351,8 @@ async function loginBySh(): Promise<boolean> {
return false
}
// 如果是设备未验证,保存响应数据
if (response.data == -1) {
// 设备未信任:后端 R.failed(-1, msg) 业务码在 code,data 为 null
if (response.code == -1) {
uni.setStorageSync(TOKEN_KEY, { data: -1 })
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { defineConfig, loadEnv } from "vite"
import uni from "@dcloudio/vite-plugin-uni"
import Unocss from "unocss/vite"
import { useRouter } from "./src/sheep"
import { useRouter } from "./src/sheep/plugs/use-router/vite-router-plug"
export default defineConfig(({ mode }) => {
const { NODE_ENV, VITE_APP_DEV_SERVICE, VITE_APP_PROD_SERVICE, VITE_IMAGE_DEV_PATH } = loadEnv(mode, process.cwd(), "")