Merge branch 'master' of https://gitea.zianxin.com/zax/biz-mobile-react-cloud
This commit is contained in:
commit
b2d022ce5f
16 files changed
+520
-35
No files matched your search
@@ -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地址 ----------------------------------
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
Vendored
+13
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -383,6 +383,13 @@
|
||||
"navigationBarTitleText": "用户身份",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "zzdSmsVerify",
|
||||
"style": {
|
||||
"navigationBarTitleText": "手机号核验",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
@@ -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 () => {
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
// 浙政钉环境
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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(), "")
|
||||
|
||||
Reference in new issue
Block a user