@liujitcn/kratos-uni-app-core 0.0.59 → 0.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.mjs +8 -4
- package/package.json +1 -1
- package/src/components/KratosSettingsPage.vue +15 -4
- package/src/components/MfaRecoveryCodesDialog.vue +12 -1
- package/src/locales/en-US.json +2 -1
- package/src/locales/ja-JP.json +2 -1
- package/src/locales/zh-CN.json +2 -1
- package/src/locales/zh-TW.json +2 -1
- package/src/stores/modules/user.ts +21 -21
- package/src/utils/file.ts +3 -3
- package/src/utils/http.ts +13 -7
- package/src/views/pages/login/login.vue +19 -14
- package/src/views/pages/webview/webview.vue +16 -6
package/dist/module.mjs
CHANGED
|
@@ -164,7 +164,8 @@ var zh_CN_default = {
|
|
|
164
164
|
"common.value.top_level": "\u9876\u7EA7\u8282\u70B9",
|
|
165
165
|
"common.resource.default": "{resource}",
|
|
166
166
|
"common.action.delete_resource": "\u5220\u9664{resource}",
|
|
167
|
-
"common.action.set_field_status": "\u8BBE\u7F6E{field}"
|
|
167
|
+
"common.action.set_field_status": "\u8BBE\u7F6E{field}",
|
|
168
|
+
"common.action.load_more": "\u52A0\u8F7D\u66F4\u591A"
|
|
168
169
|
};
|
|
169
170
|
|
|
170
171
|
// src/locales/en-US.json
|
|
@@ -308,7 +309,8 @@ var en_US_default = {
|
|
|
308
309
|
"common.value.top_level": "Top level",
|
|
309
310
|
"common.resource.default": "{resource}",
|
|
310
311
|
"common.action.delete_resource": "Delete {resource}",
|
|
311
|
-
"common.action.set_field_status": "Set {field}"
|
|
312
|
+
"common.action.set_field_status": "Set {field}",
|
|
313
|
+
"common.action.load_more": "Load more"
|
|
312
314
|
};
|
|
313
315
|
|
|
314
316
|
// src/locales/ja-JP.json
|
|
@@ -452,7 +454,8 @@ var ja_JP_default = {
|
|
|
452
454
|
"common.value.top_level": "\u30C8\u30C3\u30D7\u30EC\u30D9\u30EB",
|
|
453
455
|
"common.resource.default": "{resource}",
|
|
454
456
|
"common.action.delete_resource": "{resource}\u3092\u524A\u9664",
|
|
455
|
-
"common.action.set_field_status": "{field}\u3092\u8A2D\u5B9A"
|
|
457
|
+
"common.action.set_field_status": "{field}\u3092\u8A2D\u5B9A",
|
|
458
|
+
"common.action.load_more": "\u3055\u3089\u306B\u8AAD\u307F\u8FBC\u3080"
|
|
456
459
|
};
|
|
457
460
|
|
|
458
461
|
// src/locales/zh-TW.json
|
|
@@ -596,7 +599,8 @@ var zh_TW_default = {
|
|
|
596
599
|
"common.value.top_level": "\u9802\u7D1A\u7BC0\u9EDE",
|
|
597
600
|
"common.resource.default": "{resource}",
|
|
598
601
|
"common.action.delete_resource": "\u522A\u9664{resource}",
|
|
599
|
-
"common.action.set_field_status": "\u8A2D\u5B9A{field}"
|
|
602
|
+
"common.action.set_field_status": "\u8A2D\u5B9A{field}",
|
|
603
|
+
"common.action.load_more": "\u8F09\u5165\u66F4\u591A"
|
|
600
604
|
};
|
|
601
605
|
|
|
602
606
|
// src/locales/generated.ts
|
package/package.json
CHANGED
|
@@ -15,6 +15,15 @@ defineSlots<{
|
|
|
15
15
|
|
|
16
16
|
const userStore = useUserStore()
|
|
17
17
|
const { locale, setLocale, t } = useI18n()
|
|
18
|
+
/** 登录态失效时请求层已弹窗引导重新登录,页面侧不再叠加失败提示。 */
|
|
19
|
+
const isAuthExpiredError = (error: unknown) => {
|
|
20
|
+
if (error && typeof error === 'object') {
|
|
21
|
+
const statusCode = (error as { statusCode?: number }).statusCode
|
|
22
|
+
if (statusCode === 401 || statusCode === 403) return true
|
|
23
|
+
}
|
|
24
|
+
return error instanceof Error && /auth (required|expired)/.test(error.message)
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
const logoutLoading = ref(false)
|
|
19
28
|
const mfaDialogVisible = ref(false)
|
|
20
29
|
const mfaDialogMode = ref<'setup' | 'disable'>('setup')
|
|
@@ -136,10 +145,12 @@ const onLogout = () => {
|
|
|
136
145
|
await userStore.logout()
|
|
137
146
|
uni.navigateBack()
|
|
138
147
|
} catch (error) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
if (!isAuthExpiredError(error)) {
|
|
149
|
+
await uni.showToast({
|
|
150
|
+
icon: 'none',
|
|
151
|
+
title: t('core.settings.logout_failed'),
|
|
152
|
+
})
|
|
153
|
+
}
|
|
143
154
|
} finally {
|
|
144
155
|
logoutLoading.value = false
|
|
145
156
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, onBeforeUnmount, ref } from 'vue'
|
|
2
|
+
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
3
3
|
import { useI18n } from '../locales'
|
|
4
4
|
import { copyText } from '../utils/clipboard'
|
|
5
5
|
|
|
@@ -23,6 +23,7 @@ const emit = defineEmits<{
|
|
|
23
23
|
|
|
24
24
|
const { t } = useI18n()
|
|
25
25
|
const codesText = computed(() => props.codes.join('\n'))
|
|
26
|
+
const confirming = ref(false)
|
|
26
27
|
const copyState = ref<'idle' | 'copied' | 'failed'>('idle')
|
|
27
28
|
const copyTextLabel = computed(() => {
|
|
28
29
|
if (copyState.value === 'copied') return t('core.login.mfa_recovery_codes_copied')
|
|
@@ -48,10 +49,20 @@ async function copyRecoveryCodes() {
|
|
|
48
49
|
|
|
49
50
|
/** 关闭弹窗并通知调用方继续完成绑定流程。 */
|
|
50
51
|
function handleConfirm() {
|
|
52
|
+
if (confirming.value) return
|
|
53
|
+
confirming.value = true
|
|
51
54
|
emit('update:modelValue', false)
|
|
52
55
|
emit('confirm')
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
// 每次重新打开弹窗时重置一次性确认锁。
|
|
59
|
+
watch(
|
|
60
|
+
() => props.modelValue,
|
|
61
|
+
(open) => {
|
|
62
|
+
if (open) confirming.value = false
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
55
66
|
onBeforeUnmount(() => {
|
|
56
67
|
if (resetCopyStateTimer) clearTimeout(resetCopyStateTimer)
|
|
57
68
|
})
|
package/src/locales/en-US.json
CHANGED
|
@@ -138,5 +138,6 @@
|
|
|
138
138
|
"common.value.top_level": "Top level",
|
|
139
139
|
"common.resource.default": "{resource}",
|
|
140
140
|
"common.action.delete_resource": "Delete {resource}",
|
|
141
|
-
"common.action.set_field_status": "Set {field}"
|
|
141
|
+
"common.action.set_field_status": "Set {field}",
|
|
142
|
+
"common.action.load_more": "Load more"
|
|
142
143
|
}
|
package/src/locales/ja-JP.json
CHANGED
|
@@ -138,5 +138,6 @@
|
|
|
138
138
|
"common.value.top_level": "トップレベル",
|
|
139
139
|
"common.resource.default": "{resource}",
|
|
140
140
|
"common.action.delete_resource": "{resource}を削除",
|
|
141
|
-
"common.action.set_field_status": "{field}を設定"
|
|
141
|
+
"common.action.set_field_status": "{field}を設定",
|
|
142
|
+
"common.action.load_more": "さらに読み込む"
|
|
142
143
|
}
|
package/src/locales/zh-CN.json
CHANGED
|
@@ -138,5 +138,6 @@
|
|
|
138
138
|
"common.value.top_level": "顶级节点",
|
|
139
139
|
"common.resource.default": "{resource}",
|
|
140
140
|
"common.action.delete_resource": "删除{resource}",
|
|
141
|
-
"common.action.set_field_status": "设置{field}"
|
|
141
|
+
"common.action.set_field_status": "设置{field}",
|
|
142
|
+
"common.action.load_more": "加载更多"
|
|
142
143
|
}
|
package/src/locales/zh-TW.json
CHANGED
|
@@ -138,5 +138,6 @@
|
|
|
138
138
|
"common.value.top_level": "頂級節點",
|
|
139
139
|
"common.resource.default": "{resource}",
|
|
140
140
|
"common.action.delete_resource": "刪除{resource}",
|
|
141
|
-
"common.action.set_field_status": "設定{field}"
|
|
141
|
+
"common.action.set_field_status": "設定{field}",
|
|
142
|
+
"common.action.load_more": "載入更多"
|
|
142
143
|
}
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
CreateOauthSessionRequest,
|
|
5
5
|
CreateOauthSessionResponse,
|
|
6
6
|
} from '../../rpc/base/v1/oauth'
|
|
7
|
-
import type
|
|
7
|
+
import { LoginStatus, type LoginRequest, type LoginResponse } from '../../rpc/base/v1/login'
|
|
8
8
|
import type { VerifyMfaRequest } from '../../rpc/base/v1/mfa'
|
|
9
9
|
import { defMfaService } from '../../api/base/v1/mfa'
|
|
10
10
|
import { defAuthService } from '../../api/system/app/v1/auth'
|
|
@@ -69,6 +69,15 @@ export const useUserStore = defineStore(
|
|
|
69
69
|
return Boolean(userInfo.value && hasValidToken())
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** 仅当登录阶段已签发非空令牌时,才允许写入本地登录态。 */
|
|
73
|
+
function shouldApplyLoginToken(data: LoginResponse | CreateOauthSessionResponse) {
|
|
74
|
+
return (
|
|
75
|
+
(data.status === LoginStatus.LOGIN_STATUS_AUTHENTICATED ||
|
|
76
|
+
data.status === LoginStatus.LOGIN_STATUS_PASSWORD_CHANGE_REQUIRED) &&
|
|
77
|
+
Boolean(data.access_token && data.refresh_token)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
72
81
|
/** 保存登录接口返回的认证令牌,并通知已注册的业务扩展。 */
|
|
73
82
|
async function applyLoginToken(data: LoginResponse | CreateOauthSessionResponse) {
|
|
74
83
|
const { token_type, access_token, refresh_token, expires_in } = data
|
|
@@ -95,8 +104,7 @@ export const useUserStore = defineStore(
|
|
|
95
104
|
defLoginService
|
|
96
105
|
.Login(request)
|
|
97
106
|
.then(async (data) => {
|
|
98
|
-
if (data
|
|
99
|
-
await applyLoginToken(data)
|
|
107
|
+
if (shouldApplyLoginToken(data)) await applyLoginToken(data)
|
|
100
108
|
resolve(data)
|
|
101
109
|
})
|
|
102
110
|
.catch((error) => {
|
|
@@ -111,7 +119,7 @@ export const useUserStore = defineStore(
|
|
|
111
119
|
defMfaService
|
|
112
120
|
.VerifyMfa(request)
|
|
113
121
|
.then(async (data) => {
|
|
114
|
-
await applyLoginToken(data)
|
|
122
|
+
if (shouldApplyLoginToken(data)) await applyLoginToken(data)
|
|
115
123
|
resolve(data)
|
|
116
124
|
})
|
|
117
125
|
.catch((error) => reject(error))
|
|
@@ -133,8 +141,7 @@ export const useUserStore = defineStore(
|
|
|
133
141
|
resolve(data)
|
|
134
142
|
return
|
|
135
143
|
}
|
|
136
|
-
if (data
|
|
137
|
-
await applyLoginToken(data)
|
|
144
|
+
if (shouldApplyLoginToken(data)) await applyLoginToken(data)
|
|
138
145
|
resolve(data)
|
|
139
146
|
})
|
|
140
147
|
.catch((error) => {
|
|
@@ -149,8 +156,7 @@ export const useUserStore = defineStore(
|
|
|
149
156
|
defOauthService
|
|
150
157
|
.BindOauthSession(request)
|
|
151
158
|
.then(async (data) => {
|
|
152
|
-
if (data
|
|
153
|
-
await applyLoginToken(data)
|
|
159
|
+
if (shouldApplyLoginToken(data)) await applyLoginToken(data)
|
|
154
160
|
resolve(data)
|
|
155
161
|
})
|
|
156
162
|
.catch((error) => {
|
|
@@ -183,19 +189,13 @@ export const useUserStore = defineStore(
|
|
|
183
189
|
/**
|
|
184
190
|
* 登出
|
|
185
191
|
*/
|
|
186
|
-
function logout() {
|
|
187
|
-
|
|
188
|
-
defLoginService
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
.catch((error) => {
|
|
196
|
-
reject(error)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
192
|
+
async function logout() {
|
|
193
|
+
try {
|
|
194
|
+
await defLoginService.Logout({})
|
|
195
|
+
} finally {
|
|
196
|
+
// 登出接口失败时仍清理本地登录态,避免残留有效 token 造成“假登录”。
|
|
197
|
+
await clearUserData()
|
|
198
|
+
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
package/src/utils/file.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from '../rpc/base/v1/file'
|
|
6
6
|
import { formatSrc } from './index'
|
|
7
7
|
import { getLocaleRequestHeaders, t } from '../locales'
|
|
8
|
-
import { getRequestAccessToken } from './http'
|
|
8
|
+
import { getRequestAccessToken, sourceClient } from './http'
|
|
9
9
|
|
|
10
10
|
// 文件上传-兼容小程序端、H5端、App端
|
|
11
11
|
export const uploadFile = async (
|
|
@@ -24,7 +24,7 @@ export const uploadFile = async (
|
|
|
24
24
|
},
|
|
25
25
|
header: {
|
|
26
26
|
...getLocaleRequestHeaders(),
|
|
27
|
-
'source-client':
|
|
27
|
+
'source-client': sourceClient,
|
|
28
28
|
...(token ? { Authorization: token } : {}),
|
|
29
29
|
},
|
|
30
30
|
})
|
|
@@ -65,7 +65,7 @@ export const multiUploadFile = async (
|
|
|
65
65
|
},
|
|
66
66
|
header: {
|
|
67
67
|
...getLocaleRequestHeaders(),
|
|
68
|
-
'source-client':
|
|
68
|
+
'source-client': sourceClient,
|
|
69
69
|
...(token ? { Authorization: token } : {}),
|
|
70
70
|
},
|
|
71
71
|
})
|
package/src/utils/http.ts
CHANGED
|
@@ -22,7 +22,7 @@ const apiBasePath = import.meta.env.VITE_APP_BASE_API || '/api'
|
|
|
22
22
|
const apiTargetUrl = import.meta.env.VITE_APP_API_URL || ''
|
|
23
23
|
const normalizedApiBasePath = apiBasePath.startsWith('/') ? apiBasePath : `/${apiBasePath}`
|
|
24
24
|
|
|
25
|
-
let sourceClient = 'uni-h5'
|
|
25
|
+
export let sourceClient = 'uni-h5'
|
|
26
26
|
// #ifdef MP-WEIXIN
|
|
27
27
|
sourceClient = 'uni-weapp'
|
|
28
28
|
// #endif
|
|
@@ -37,6 +37,11 @@ const requestOrigin =
|
|
|
37
37
|
: apiTargetUrl.replace(/\/$/, '')
|
|
38
38
|
const baseURL = `${requestOrigin}${normalizedApiBasePath}`
|
|
39
39
|
export const requestBaseURL = baseURL
|
|
40
|
+
/** 站点根地址(不含 API 基础路径),供事件流等非 API 端点复用。 */
|
|
41
|
+
export const siteBaseURL =
|
|
42
|
+
typeof window !== 'undefined' && window.location?.protocol?.startsWith('http')
|
|
43
|
+
? window.location.origin
|
|
44
|
+
: apiTargetUrl.replace(/\/$/, '')
|
|
40
45
|
const SESSION_URL = '/v1/base/session'
|
|
41
46
|
const REFRESH_TOKEN_URL = '/v1/base/token'
|
|
42
47
|
const CAPTCHA_URL = '/v1/base/captcha'
|
|
@@ -265,18 +270,15 @@ export function handleAuthExpired(authMode: AuthMode = 'required') {
|
|
|
265
270
|
}
|
|
266
271
|
|
|
267
272
|
// 刷新 Token 处理
|
|
268
|
-
function handleTokenRefresh(
|
|
273
|
+
function handleTokenRefresh() {
|
|
269
274
|
if (refreshTokenPromise) {
|
|
270
275
|
return refreshTokenPromise
|
|
271
276
|
}
|
|
272
277
|
isRefreshing = true
|
|
273
278
|
refreshTokenPromise = refreshAccessToken()
|
|
274
|
-
.catch(
|
|
279
|
+
.catch((error) => {
|
|
275
280
|
// 刷新失败后立即撤销本地认证,避免弹窗等待期间后台请求反复提交旧刷新令牌。
|
|
276
281
|
silentClearAuthData()
|
|
277
|
-
if (authMode === 'required') {
|
|
278
|
-
await promptRelogin()
|
|
279
|
-
}
|
|
280
282
|
throw error
|
|
281
283
|
})
|
|
282
284
|
.finally(() => {
|
|
@@ -300,7 +302,11 @@ async function getAccessTokenByMode(authMode: AuthMode) {
|
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
if (shouldRefreshToken()) {
|
|
303
|
-
|
|
305
|
+
try {
|
|
306
|
+
await handleTokenRefresh()
|
|
307
|
+
} catch {
|
|
308
|
+
// 刷新失败已撤销本地认证,交由下方 hasValidToken 分支按当前调用方 authMode 处理。
|
|
309
|
+
}
|
|
304
310
|
}
|
|
305
311
|
|
|
306
312
|
if (hasValidToken()) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useSettingStore, useUserStore } from '../../../stores'
|
|
3
3
|
import type { LoginRequest } from '../../../rpc/base/v1/login'
|
|
4
4
|
import { LoginStatus, type LoginResponse } from '../../../rpc/base/v1/login'
|
|
5
|
-
import { onLoad } from '@dcloudio/uni-app'
|
|
5
|
+
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
|
6
6
|
import { computed, defineAsyncComponent, nextTick, reactive, ref, watch } from 'vue'
|
|
7
7
|
import { defLoginService } from '../../../api/base/v1/login'
|
|
8
8
|
import { defMfaService } from '../../../api/base/v1/mfa'
|
|
@@ -102,10 +102,15 @@ const toggleAgreePrivacy = () => {
|
|
|
102
102
|
isAgreePrivacy.value = !isAgreePrivacy.value
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
let agreePrivacyShakeTimer: ReturnType<typeof setTimeout> | undefined
|
|
106
|
+
let loginSuccessTimer: ReturnType<typeof setTimeout> | undefined
|
|
107
|
+
|
|
105
108
|
const triggerAgreePrivacyShake = () => {
|
|
106
109
|
isAgreePrivacyShakeY.value = true
|
|
107
|
-
|
|
110
|
+
if (agreePrivacyShakeTimer) clearTimeout(agreePrivacyShakeTimer)
|
|
111
|
+
agreePrivacyShakeTimer = setTimeout(() => {
|
|
108
112
|
isAgreePrivacyShakeY.value = false
|
|
113
|
+
agreePrivacyShakeTimer = undefined
|
|
109
114
|
}, 500)
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -203,17 +208,8 @@ const refreshMiniCaptcha = async () => {
|
|
|
203
208
|
/** 仅将后端明确返回的未绑定错误转为账号绑定流程。 */
|
|
204
209
|
const isWechatUnboundError = (error: unknown) => {
|
|
205
210
|
if (!error || typeof error !== 'object') return false
|
|
206
|
-
const response = error as {
|
|
207
|
-
|
|
208
|
-
data?: {
|
|
209
|
-
reason?: string | number
|
|
210
|
-
message?: string
|
|
211
|
-
binding_required?: boolean
|
|
212
|
-
error?: { reason?: string | number; message?: string }
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const reason = response.data?.reason ?? response.data?.error?.reason
|
|
216
|
-
return response.data?.binding_required === true || String(reason || '') === 'UNAUTHENTICATED'
|
|
211
|
+
const response = error as { data?: { binding_required?: boolean } }
|
|
212
|
+
return response.data?.binding_required === true
|
|
217
213
|
}
|
|
218
214
|
|
|
219
215
|
// 微信授权失败时保留请求层提示,避免异步登录异常冒泡到小程序调试器。
|
|
@@ -781,7 +777,9 @@ const loginSuccess = async () => {
|
|
|
781
777
|
await userStore.getUserProfile()
|
|
782
778
|
// 成功提示
|
|
783
779
|
await uni.showToast({ icon: 'success', title: t('core.login.login_success') })
|
|
784
|
-
|
|
780
|
+
if (loginSuccessTimer) clearTimeout(loginSuccessTimer)
|
|
781
|
+
loginSuccessTimer = setTimeout(() => {
|
|
782
|
+
loginSuccessTimer = undefined
|
|
785
783
|
const lastRoute = uni.getStorageSync('lastRoute') || homeTabPage
|
|
786
784
|
if (lastRoute.startsWith(homeTabPage)) {
|
|
787
785
|
uni.setStorageSync('SwitchTabIndex', true)
|
|
@@ -873,6 +871,13 @@ const ensureLoginSettings = () => loadLoginSettings()
|
|
|
873
871
|
onLoad(() => {
|
|
874
872
|
void loadLoginSettings()
|
|
875
873
|
})
|
|
874
|
+
|
|
875
|
+
onUnload(() => {
|
|
876
|
+
if (agreePrivacyShakeTimer) clearTimeout(agreePrivacyShakeTimer)
|
|
877
|
+
agreePrivacyShakeTimer = undefined
|
|
878
|
+
if (loginSuccessTimer) clearTimeout(loginSuccessTimer)
|
|
879
|
+
loginSuccessTimer = undefined
|
|
880
|
+
})
|
|
876
881
|
</script>
|
|
877
882
|
|
|
878
883
|
<template>
|
|
@@ -41,6 +41,17 @@ const isIframeLoaded = ref(false)
|
|
|
41
41
|
const isIframeTimedOut = ref(false)
|
|
42
42
|
const { t } = useI18n()
|
|
43
43
|
|
|
44
|
+
/** 只放行 http(s) 外链,其它协议按非法地址处理。 */
|
|
45
|
+
const isAllowedUrl = (value: string) => /^https?:\/\//i.test(value)
|
|
46
|
+
|
|
47
|
+
const safeDecodeURIComponent = (value: string) => {
|
|
48
|
+
try {
|
|
49
|
+
return decodeURIComponent(value)
|
|
50
|
+
} catch {
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
const showFallback = computed(
|
|
45
56
|
() => !url.value || (isH5.value && isIframeTimedOut.value && !isIframeLoaded.value),
|
|
46
57
|
)
|
|
@@ -52,8 +63,9 @@ const emptyDesc = computed(() => {
|
|
|
52
63
|
})
|
|
53
64
|
|
|
54
65
|
onLoad((query) => {
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
const nextUrl = safeDecodeURIComponent(query?.url || '')
|
|
67
|
+
url.value = isAllowedUrl(nextUrl) ? nextUrl : ''
|
|
68
|
+
const title = safeDecodeURIComponent(query?.title || '')
|
|
57
69
|
// 调用方传入标题时同步更新原生导航栏,避免继续显示页面注册时的空标题。
|
|
58
70
|
if (title) {
|
|
59
71
|
void uni.setNavigationBarTitle({ title })
|
|
@@ -81,10 +93,8 @@ const openInBrowser = () => {
|
|
|
81
93
|
// #endif
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
//
|
|
85
|
-
const handleMessage = (
|
|
86
|
-
console.log('收到H5消息:', e.detail)
|
|
87
|
-
}
|
|
96
|
+
// 小程序接收消息(第三方消息不落日志,避免敏感信息泄漏)。
|
|
97
|
+
const handleMessage = () => {}
|
|
88
98
|
</script>
|
|
89
99
|
|
|
90
100
|
<style scoped>
|