@liujitcn/kratos-uni-app-core 0.0.20 → 0.0.23
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/README.md +1 -1
- package/dist/module.mjs +780 -4
- package/dist/vite.mjs +5 -3
- package/package.json +1 -1
- package/src/api/base/language.ts +25 -0
- package/src/bootstrap.ts +21 -0
- package/src/components/BootstrapStatus.vue +4 -1
- package/src/index.ts +1 -0
- package/src/locales/README.md +15 -0
- package/src/locales/en-US.json +107 -0
- package/src/locales/es-ES.json +107 -0
- package/src/locales/fr-FR.json +107 -0
- package/src/locales/generated.ts +22 -0
- package/src/locales/index.ts +216 -0
- package/src/locales/ja-JP.json +107 -0
- package/src/locales/ko-KR.json +107 -0
- package/src/locales/zh-CN.json +107 -0
- package/src/locales/zh-TW.json +107 -0
- package/src/module.ts +7 -0
- package/src/navigation.ts +58 -27
- package/src/pages.ts +3 -3
- package/src/rpc/base/v1/config.ts +14 -1
- package/src/rpc/base/v1/language.ts +31 -0
- package/src/rpc/base/v1/login.ts +14 -1
- package/src/stores/index.ts +1 -0
- package/src/stores/modules/locale.ts +28 -0
- package/src/stores/modules/setting.ts +11 -6
- package/src/utils/file.ts +19 -26
- package/src/utils/http.ts +12 -6
- package/src/utils/passwordCrypto.ts +17 -16
- package/src/views/pages/index/index.vue +16 -14
- package/src/views/pages/login/login.vue +125 -57
- package/src/views/pages/login/protocal.vue +5 -3
- package/src/views/pages/status/index.vue +16 -18
- package/src/views/pages/webview/webview.vue +6 -4
- package/src/vite.ts +4 -3
- package/src/rpc/base/v1/enum.ts +0 -54
- package/src/rpc/system/common/v1/enum.ts +0 -80
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { useSettingStore, useUserStore } from '../../../stores'
|
|
3
3
|
import type { LoginRequest } from '../../../rpc/base/v1/login'
|
|
4
4
|
import { onLoad } from '@dcloudio/uni-app'
|
|
5
|
-
import { computed, defineAsyncComponent, reactive, ref } from 'vue'
|
|
5
|
+
import { computed, defineAsyncComponent, reactive, ref, watch } from 'vue'
|
|
6
6
|
import { defLoginService } from '../../../api/base/login'
|
|
7
7
|
import defaultLogo from '../../../static/images/logo_icon.png'
|
|
8
8
|
import { homeTabPage } from '../../../utils/navigation'
|
|
9
9
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from '../../../utils/passwordCrypto'
|
|
10
10
|
import { navigateAppRoute } from '../../../navigation'
|
|
11
|
+
import { getLanguageOptions, useI18n, type SupportedLocale } from '../../../locales'
|
|
11
12
|
|
|
12
13
|
// go-captcha-uni 仅在 H5 端使用,小程序端不支持 defineAsyncComponent,需按平台裁剪。
|
|
13
14
|
// #ifdef H5
|
|
@@ -16,12 +17,25 @@ const GoCaptchaUni = defineAsyncComponent(() => import('go-captcha-uni'))
|
|
|
16
17
|
|
|
17
18
|
const userStore = useUserStore()
|
|
18
19
|
const settingStore = useSettingStore()
|
|
20
|
+
const { locale, setLocale, t } = useI18n()
|
|
21
|
+
const localeOptions = computed(() => getLanguageOptions())
|
|
22
|
+
const localeLabels = computed(() => localeOptions.value.map((item) => item.native_name))
|
|
23
|
+
const localeIndex = computed(() =>
|
|
24
|
+
Math.max(
|
|
25
|
+
0,
|
|
26
|
+
localeOptions.value.findIndex((item) => item.language_code === locale.value),
|
|
27
|
+
),
|
|
28
|
+
)
|
|
19
29
|
const wechatMiniProvider = 'wechatmini'
|
|
20
30
|
const loginSettingsReady = ref(false)
|
|
21
31
|
let loginSettingsPromise: Promise<boolean> | undefined
|
|
22
32
|
|
|
23
|
-
const configuredMainTitle = computed(
|
|
24
|
-
|
|
33
|
+
const configuredMainTitle = computed(
|
|
34
|
+
() => settingStore.getData('mainTitle') || t('core.home.main_title'),
|
|
35
|
+
)
|
|
36
|
+
const configuredSubTitle = computed(
|
|
37
|
+
() => settingStore.getData('subTitle') || t('core.login.default_sub_title'),
|
|
38
|
+
)
|
|
25
39
|
const configuredAppLogo = computed(() => settingStore.getData('appLogo') || defaultLogo)
|
|
26
40
|
const showTenantCode = computed(() => settingStore.getData('showTenantCode') !== 'false')
|
|
27
41
|
|
|
@@ -30,6 +44,18 @@ const isAgreePrivacy = ref(false)
|
|
|
30
44
|
const isAgreePrivacyShakeY = ref(false)
|
|
31
45
|
const loading = ref(false)
|
|
32
46
|
|
|
47
|
+
watch(locale, () => void uni.setNavigationBarTitle({ title: t('common.action.login') }), {
|
|
48
|
+
immediate: true,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/** 切换登录页语言。 */
|
|
52
|
+
const onLocaleChange = (event: { detail: { value: string | number } }) => {
|
|
53
|
+
const nextLocale = localeOptions.value[Number(event.detail.value)]?.language_code as
|
|
54
|
+
| SupportedLocale
|
|
55
|
+
| undefined
|
|
56
|
+
if (nextLocale) void setLocale(nextLocale)
|
|
57
|
+
}
|
|
58
|
+
|
|
33
59
|
const toggleAgreePrivacy = () => {
|
|
34
60
|
isAgreePrivacy.value = !isAgreePrivacy.value
|
|
35
61
|
}
|
|
@@ -95,7 +121,7 @@ const resolveMiniCaptchaImage = (payload: string, captchaId: string) => {
|
|
|
95
121
|
data: payload.slice(commaIndex + 1),
|
|
96
122
|
encoding: 'base64',
|
|
97
123
|
success: () => resolve(filePath),
|
|
98
|
-
fail: () => reject(new Error('
|
|
124
|
+
fail: () => reject(new Error(t('core.login.captcha_write_failed'))),
|
|
99
125
|
})
|
|
100
126
|
})
|
|
101
127
|
}
|
|
@@ -111,7 +137,7 @@ const refreshMiniCaptcha = async () => {
|
|
|
111
137
|
miniForm.captcha_id = captcha.captcha_id
|
|
112
138
|
miniForm.captcha_code = ''
|
|
113
139
|
} catch {
|
|
114
|
-
await uni.showToast({ icon: 'none', title: '
|
|
140
|
+
await uni.showToast({ icon: 'none', title: t('core.login.captcha_load_failed') })
|
|
115
141
|
}
|
|
116
142
|
}
|
|
117
143
|
|
|
@@ -128,12 +154,7 @@ const isWechatUnboundError = (error: unknown) => {
|
|
|
128
154
|
}
|
|
129
155
|
}
|
|
130
156
|
const reason = response.data?.reason ?? response.data?.error?.reason
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
response.data?.binding_required === true ||
|
|
134
|
-
String(reason || '') === 'UNAUTHENTICATED' ||
|
|
135
|
-
message === '微信账号未绑定,请先绑定已有账号'
|
|
136
|
-
)
|
|
157
|
+
return response.data?.binding_required === true || String(reason || '') === 'UNAUTHENTICATED'
|
|
137
158
|
}
|
|
138
159
|
|
|
139
160
|
// 微信授权失败时保留请求层提示,避免异步登录异常冒泡到小程序调试器。
|
|
@@ -150,7 +171,7 @@ const wxLogin = async () => {
|
|
|
150
171
|
try {
|
|
151
172
|
loginCode = (await wx.login()).code
|
|
152
173
|
} catch {
|
|
153
|
-
await uni.showToast({ icon: 'none', title: '
|
|
174
|
+
await uni.showToast({ icon: 'none', title: t('core.login.wechat_failed') })
|
|
154
175
|
loading.value = false
|
|
155
176
|
return
|
|
156
177
|
}
|
|
@@ -167,7 +188,7 @@ const wxLogin = async () => {
|
|
|
167
188
|
await loginSuccess()
|
|
168
189
|
} catch (error) {
|
|
169
190
|
if (!isWechatUnboundError(error)) {
|
|
170
|
-
await uni.showToast({ icon: 'none', title: '
|
|
191
|
+
await uni.showToast({ icon: 'none', title: t('core.login.wechat_failed') })
|
|
171
192
|
return
|
|
172
193
|
}
|
|
173
194
|
miniBinding.value = true
|
|
@@ -182,20 +203,23 @@ const bindMiniAccount = async () => {
|
|
|
182
203
|
if (loading.value) return
|
|
183
204
|
if (!(await checkedAgreePrivacy())) return
|
|
184
205
|
if (showTenantCode.value && !miniForm.tenant_code) {
|
|
185
|
-
await uni.showToast({ icon: 'none', title: '
|
|
206
|
+
await uni.showToast({ icon: 'none', title: t('core.login.tenant') })
|
|
186
207
|
return
|
|
187
208
|
}
|
|
188
209
|
if (!miniForm.user_name || !miniPasswordValue.value) {
|
|
189
|
-
await uni.showToast({ icon: 'none', title: '
|
|
210
|
+
await uni.showToast({ icon: 'none', title: t('core.login.user_name_password') })
|
|
190
211
|
return
|
|
191
212
|
}
|
|
192
213
|
if (!miniForm.captcha_id || !miniForm.captcha_code) {
|
|
193
|
-
await uni.showToast({ icon: 'none', title: '
|
|
214
|
+
await uni.showToast({ icon: 'none', title: t('core.login.captcha') })
|
|
194
215
|
return
|
|
195
216
|
}
|
|
196
217
|
loading.value = true
|
|
197
218
|
try {
|
|
198
|
-
const password = await encryptPassword(
|
|
219
|
+
const password = await encryptPassword(
|
|
220
|
+
miniPasswordValue.value,
|
|
221
|
+
PASSWORD_CRYPTO_SCENE.PASSWORD_CRYPTO_SCENE_LOGIN,
|
|
222
|
+
)
|
|
199
223
|
const code = (await wx.login()).code
|
|
200
224
|
await userStore.bindOauthSession({
|
|
201
225
|
provider: wechatMiniProvider,
|
|
@@ -317,7 +341,7 @@ const behaviorCaptchaConfig = computed(() => {
|
|
|
317
341
|
verticalPadding: 0,
|
|
318
342
|
horizontalPadding: 0,
|
|
319
343
|
iconSize: 20,
|
|
320
|
-
title: '
|
|
344
|
+
title: t('core.login.behavior_rotate'),
|
|
321
345
|
}
|
|
322
346
|
}
|
|
323
347
|
return {
|
|
@@ -328,10 +352,13 @@ const behaviorCaptchaConfig = computed(() => {
|
|
|
328
352
|
showTheme: false,
|
|
329
353
|
verticalPadding: 0,
|
|
330
354
|
horizontalPadding: 0,
|
|
331
|
-
buttonText: '
|
|
355
|
+
buttonText: t('common.action.confirm'),
|
|
332
356
|
iconSize: 20,
|
|
333
357
|
dotSize: 20,
|
|
334
|
-
title:
|
|
358
|
+
title:
|
|
359
|
+
currentCaptchaType.value === 'click'
|
|
360
|
+
? t('core.login.behavior_click')
|
|
361
|
+
: t('core.login.behavior_puzzle'),
|
|
335
362
|
}
|
|
336
363
|
})
|
|
337
364
|
const behaviorCaptchaTheme = {
|
|
@@ -353,11 +380,11 @@ const behaviorCaptchaTheme = {
|
|
|
353
380
|
const getCaptcha = async () => {
|
|
354
381
|
const requestedCaptchaType = configuredCaptchaType.value
|
|
355
382
|
if (!requestedCaptchaType) {
|
|
356
|
-
throw new Error('
|
|
383
|
+
throw new Error(t('core.login.captcha_type_missing'))
|
|
357
384
|
}
|
|
358
385
|
const data = await defLoginService.Captcha({ type: requestedCaptchaType })
|
|
359
386
|
if (!data.type) {
|
|
360
|
-
throw new Error('
|
|
387
|
+
throw new Error(t('core.login.captcha_type_response_missing'))
|
|
361
388
|
}
|
|
362
389
|
currentCaptchaType.value = data.type
|
|
363
390
|
if (!isBehaviorCaptcha.value) {
|
|
@@ -381,7 +408,7 @@ const refreshCaptcha = async () => {
|
|
|
381
408
|
behaviorDialogVisible.value = false
|
|
382
409
|
await uni.showToast({
|
|
383
410
|
icon: 'none',
|
|
384
|
-
title: error instanceof Error ? error.message : '
|
|
411
|
+
title: error instanceof Error ? error.message : t('core.login.captcha_load_failed'),
|
|
385
412
|
})
|
|
386
413
|
return false
|
|
387
414
|
}
|
|
@@ -442,28 +469,28 @@ const validateLoginForm = async () => {
|
|
|
442
469
|
if (showTenantCode.value && !form.value.tenant_code) {
|
|
443
470
|
await uni.showToast({
|
|
444
471
|
icon: 'none',
|
|
445
|
-
title: '
|
|
472
|
+
title: t('core.login.tenant'),
|
|
446
473
|
})
|
|
447
474
|
return false
|
|
448
475
|
}
|
|
449
476
|
if (!form.value.user_name) {
|
|
450
477
|
await uni.showToast({
|
|
451
478
|
icon: 'none',
|
|
452
|
-
title: '
|
|
479
|
+
title: t('core.login.user_name'),
|
|
453
480
|
})
|
|
454
481
|
return false
|
|
455
482
|
}
|
|
456
483
|
if (!passwordValue.value) {
|
|
457
484
|
await uni.showToast({
|
|
458
485
|
icon: 'none',
|
|
459
|
-
title: '
|
|
486
|
+
title: t('core.login.password'),
|
|
460
487
|
})
|
|
461
488
|
return false
|
|
462
489
|
}
|
|
463
490
|
if (!isBehaviorCaptcha.value && !form.value.captcha_code) {
|
|
464
491
|
await uni.showToast({
|
|
465
492
|
icon: 'none',
|
|
466
|
-
title: '
|
|
493
|
+
title: t('core.login.captcha'),
|
|
467
494
|
})
|
|
468
495
|
return false
|
|
469
496
|
}
|
|
@@ -479,7 +506,10 @@ const verifyCaptchaToken = async (captchaCode: string) => {
|
|
|
479
506
|
}
|
|
480
507
|
// 执行真正的账号登录流程。
|
|
481
508
|
const submitLogin = async (captchaCode: string) => {
|
|
482
|
-
const password = await encryptPassword(
|
|
509
|
+
const password = await encryptPassword(
|
|
510
|
+
passwordValue.value,
|
|
511
|
+
PASSWORD_CRYPTO_SCENE.PASSWORD_CRYPTO_SCENE_LOGIN,
|
|
512
|
+
)
|
|
483
513
|
return userStore.login({
|
|
484
514
|
...form.value,
|
|
485
515
|
tenant_code: showTenantCode.value ? form.value.tenant_code : '0000',
|
|
@@ -567,7 +597,7 @@ const onSubmit = async () => {
|
|
|
567
597
|
const loginSuccess = async () => {
|
|
568
598
|
await userStore.getUserProfile()
|
|
569
599
|
// 成功提示
|
|
570
|
-
await uni.showToast({ icon: 'success', title: '
|
|
600
|
+
await uni.showToast({ icon: 'success', title: t('core.login.login_success') })
|
|
571
601
|
setTimeout(() => {
|
|
572
602
|
const lastRoute = uni.getStorageSync('lastRoute') || homeTabPage
|
|
573
603
|
if (lastRoute.startsWith(homeTabPage)) {
|
|
@@ -586,7 +616,7 @@ const checkedAgreePrivacy = async () => {
|
|
|
586
616
|
}
|
|
587
617
|
|
|
588
618
|
if (!settingStore.getData('serviceProtocol') || !settingStore.getData('privacyProtocol')) {
|
|
589
|
-
await uni.showToast({ icon: 'none', title: '
|
|
619
|
+
await uni.showToast({ icon: 'none', title: t('core.login.protocol_missing') })
|
|
590
620
|
return false
|
|
591
621
|
}
|
|
592
622
|
|
|
@@ -598,10 +628,10 @@ const checkedAgreePrivacy = async () => {
|
|
|
598
628
|
|
|
599
629
|
return new Promise<boolean>((resolve) => {
|
|
600
630
|
uni.showModal({
|
|
601
|
-
title: '
|
|
602
|
-
content: '
|
|
603
|
-
confirmText: '
|
|
604
|
-
cancelText: '
|
|
631
|
+
title: t('common.title.notice'),
|
|
632
|
+
content: t('core.login.protocol_prompt'),
|
|
633
|
+
confirmText: t('common.action.confirm'),
|
|
634
|
+
cancelText: t('common.action.cancel'),
|
|
605
635
|
success: ({ confirm }) => {
|
|
606
636
|
if (confirm) {
|
|
607
637
|
isAgreePrivacy.value = true
|
|
@@ -630,7 +660,7 @@ const loadLoginSettings = () => {
|
|
|
630
660
|
// #ifdef H5
|
|
631
661
|
const captchaType = configuredCaptchaType.value
|
|
632
662
|
if (!captchaType) {
|
|
633
|
-
throw new Error('
|
|
663
|
+
throw new Error(t('core.login.captcha_type_missing'))
|
|
634
664
|
}
|
|
635
665
|
currentCaptchaType.value = captchaType
|
|
636
666
|
await loadPageCaptcha()
|
|
@@ -643,7 +673,7 @@ const loadLoginSettings = () => {
|
|
|
643
673
|
} catch (error) {
|
|
644
674
|
await uni.showToast({
|
|
645
675
|
icon: 'none',
|
|
646
|
-
title: error instanceof Error ? error.message : '
|
|
676
|
+
title: error instanceof Error ? error.message : t('core.protocol.load_failed'),
|
|
647
677
|
})
|
|
648
678
|
loginSettingsPromise = undefined
|
|
649
679
|
return false
|
|
@@ -664,6 +694,14 @@ onLoad(() => {
|
|
|
664
694
|
|
|
665
695
|
<template>
|
|
666
696
|
<view class="login-page">
|
|
697
|
+
<picker
|
|
698
|
+
class="login-locale"
|
|
699
|
+
:range="localeLabels"
|
|
700
|
+
:value="localeIndex"
|
|
701
|
+
@change="onLocaleChange"
|
|
702
|
+
>
|
|
703
|
+
<view class="login-locale__value">{{ localeLabels[localeIndex] }}</view>
|
|
704
|
+
</picker>
|
|
667
705
|
<view class="login-hero">
|
|
668
706
|
<view class="login-logo-shell">
|
|
669
707
|
<image :src="configuredAppLogo" />
|
|
@@ -683,14 +721,14 @@ onLoad(() => {
|
|
|
683
721
|
class="login-input"
|
|
684
722
|
type="text"
|
|
685
723
|
confirm-type="next"
|
|
686
|
-
placeholder="
|
|
724
|
+
:placeholder="t('core.login.tenant')"
|
|
687
725
|
/>
|
|
688
726
|
<input
|
|
689
727
|
v-model="form.user_name"
|
|
690
728
|
class="login-input"
|
|
691
729
|
type="text"
|
|
692
730
|
confirm-type="next"
|
|
693
|
-
placeholder="
|
|
731
|
+
:placeholder="t('core.login.user_name_mobile')"
|
|
694
732
|
@confirm="onSubmit"
|
|
695
733
|
/>
|
|
696
734
|
<input
|
|
@@ -699,7 +737,7 @@ onLoad(() => {
|
|
|
699
737
|
type="text"
|
|
700
738
|
password
|
|
701
739
|
confirm-type="done"
|
|
702
|
-
placeholder="
|
|
740
|
+
:placeholder="t('core.login.password')"
|
|
703
741
|
@confirm="onSubmit"
|
|
704
742
|
/>
|
|
705
743
|
<view v-if="!isBehaviorCaptcha" class="captcha-row">
|
|
@@ -708,7 +746,7 @@ onLoad(() => {
|
|
|
708
746
|
class="login-input captcha-input"
|
|
709
747
|
type="text"
|
|
710
748
|
confirm-type="done"
|
|
711
|
-
placeholder="
|
|
749
|
+
:placeholder="t('core.login.captcha')"
|
|
712
750
|
@confirm="onSubmit"
|
|
713
751
|
/>
|
|
714
752
|
<view class="captcha-divider"></view>
|
|
@@ -722,10 +760,14 @@ onLoad(() => {
|
|
|
722
760
|
/>
|
|
723
761
|
</view>
|
|
724
762
|
</view>
|
|
725
|
-
<button @tap="onSubmit" class="login-button login-button-primary"
|
|
763
|
+
<button @tap="onSubmit" class="login-button login-button-primary">
|
|
764
|
+
{{ t('common.action.login') }}
|
|
765
|
+
</button>
|
|
726
766
|
<view v-if="behaviorDialogVisible" class="login-behavior-mask">
|
|
727
767
|
<view class="login-behavior-panel">
|
|
728
|
-
<view v-if="behaviorLoading" class="login-behavior-loading"
|
|
768
|
+
<view v-if="behaviorLoading" class="login-behavior-loading">{{
|
|
769
|
+
t('core.login.behavior_loading')
|
|
770
|
+
}}</view>
|
|
729
771
|
<GoCaptchaUni
|
|
730
772
|
:type="currentCaptchaType"
|
|
731
773
|
:data="behaviorCaptchaData"
|
|
@@ -748,36 +790,36 @@ onLoad(() => {
|
|
|
748
790
|
@tap="wxLogin"
|
|
749
791
|
>
|
|
750
792
|
<text class="icon icon-phone"></text>
|
|
751
|
-
|
|
793
|
+
{{ t('core.login.wechat') }}
|
|
752
794
|
</button>
|
|
753
795
|
<view v-else class="login-form">
|
|
754
|
-
<view class="login-bind-tip"
|
|
796
|
+
<view class="login-bind-tip">{{ t('core.login.wechat_bind_tip') }}</view>
|
|
755
797
|
<input
|
|
756
798
|
v-if="showTenantCode"
|
|
757
799
|
v-model="miniForm.tenant_code"
|
|
758
800
|
class="login-input"
|
|
759
801
|
type="text"
|
|
760
|
-
placeholder="
|
|
802
|
+
:placeholder="t('core.login.tenant')"
|
|
761
803
|
/>
|
|
762
804
|
<input
|
|
763
805
|
v-model="miniForm.user_name"
|
|
764
806
|
class="login-input"
|
|
765
807
|
type="text"
|
|
766
|
-
placeholder="
|
|
808
|
+
:placeholder="t('core.login.user_name_mobile')"
|
|
767
809
|
/>
|
|
768
810
|
<input
|
|
769
811
|
v-model="miniPasswordValue"
|
|
770
812
|
class="login-input"
|
|
771
813
|
type="text"
|
|
772
814
|
password
|
|
773
|
-
placeholder="
|
|
815
|
+
:placeholder="t('core.login.password')"
|
|
774
816
|
/>
|
|
775
817
|
<view class="captcha-row">
|
|
776
818
|
<input
|
|
777
819
|
v-model="miniForm.captcha_code"
|
|
778
820
|
class="login-input captcha-input"
|
|
779
821
|
type="text"
|
|
780
|
-
placeholder="
|
|
822
|
+
:placeholder="t('core.login.captcha')"
|
|
781
823
|
/>
|
|
782
824
|
<view class="captcha-divider"></view>
|
|
783
825
|
<view class="captcha-trigger" @tap="refreshMiniCaptcha">
|
|
@@ -789,7 +831,7 @@ onLoad(() => {
|
|
|
789
831
|
:loading="loading"
|
|
790
832
|
@tap="bindMiniAccount"
|
|
791
833
|
>
|
|
792
|
-
|
|
834
|
+
{{ t('core.login.bind_wechat') }}
|
|
793
835
|
</button>
|
|
794
836
|
</view>
|
|
795
837
|
<!-- #endif -->
|
|
@@ -797,10 +839,14 @@ onLoad(() => {
|
|
|
797
839
|
<view class="login-tips" :class="{ animate__shakeY: isAgreePrivacyShakeY }">
|
|
798
840
|
<view class="login-agreement" @tap="toggleAgreePrivacy">
|
|
799
841
|
<view class="login-agree-icon" :class="{ checked: isAgreePrivacy }"></view>
|
|
800
|
-
<text class="login-agree-desc"
|
|
801
|
-
<text class="login-agree-link" @tap.stop="onOpenServiceProtocol"
|
|
802
|
-
|
|
803
|
-
|
|
842
|
+
<text class="login-agree-desc">{{ t('core.login.agree_prefix') }}</text>
|
|
843
|
+
<text class="login-agree-link" @tap.stop="onOpenServiceProtocol">{{
|
|
844
|
+
t('core.login.service')
|
|
845
|
+
}}</text>
|
|
846
|
+
<text class="login-agree-separator">{{ t('core.login.agree_separator') }}</text>
|
|
847
|
+
<text class="login-agree-link" @tap.stop="onOpenPrivacyContract">{{
|
|
848
|
+
t('core.login.privacy')
|
|
849
|
+
}}</text>
|
|
804
850
|
</view>
|
|
805
851
|
</view>
|
|
806
852
|
</view>
|
|
@@ -809,6 +855,7 @@ onLoad(() => {
|
|
|
809
855
|
|
|
810
856
|
<style lang="scss">
|
|
811
857
|
.login-page {
|
|
858
|
+
position: relative;
|
|
812
859
|
display: flex;
|
|
813
860
|
flex-direction: column;
|
|
814
861
|
justify-content: center;
|
|
@@ -818,6 +865,19 @@ onLoad(() => {
|
|
|
818
865
|
box-sizing: border-box;
|
|
819
866
|
}
|
|
820
867
|
|
|
868
|
+
.login-locale {
|
|
869
|
+
position: absolute;
|
|
870
|
+
top: calc(24rpx + env(safe-area-inset-top));
|
|
871
|
+
right: 28rpx;
|
|
872
|
+
z-index: 2;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.login-locale__value {
|
|
876
|
+
padding: 12rpx 16rpx;
|
|
877
|
+
color: #16806d;
|
|
878
|
+
font-size: 24rpx;
|
|
879
|
+
}
|
|
880
|
+
|
|
821
881
|
.login-hero {
|
|
822
882
|
margin-bottom: 40rpx;
|
|
823
883
|
text-align: center;
|
|
@@ -1095,16 +1155,22 @@ onLoad(() => {
|
|
|
1095
1155
|
.login-agreement {
|
|
1096
1156
|
display: flex;
|
|
1097
1157
|
align-items: center;
|
|
1158
|
+
flex-wrap: wrap;
|
|
1098
1159
|
justify-content: center;
|
|
1160
|
+
width: 100%;
|
|
1161
|
+
max-width: 100%;
|
|
1162
|
+
min-width: 0;
|
|
1099
1163
|
gap: 6rpx;
|
|
1100
|
-
|
|
1164
|
+
box-sizing: border-box;
|
|
1101
1165
|
}
|
|
1102
1166
|
|
|
1103
1167
|
.login-agree-separator {
|
|
1104
|
-
|
|
1168
|
+
min-width: 0;
|
|
1169
|
+
overflow-wrap: anywhere;
|
|
1105
1170
|
}
|
|
1106
1171
|
|
|
1107
1172
|
.login-agree-icon {
|
|
1173
|
+
flex: 0 0 auto;
|
|
1108
1174
|
width: 26rpx;
|
|
1109
1175
|
height: 26rpx;
|
|
1110
1176
|
margin-right: 2rpx;
|
|
@@ -1121,13 +1187,15 @@ onLoad(() => {
|
|
|
1121
1187
|
}
|
|
1122
1188
|
|
|
1123
1189
|
.login-agree-link {
|
|
1124
|
-
|
|
1190
|
+
min-width: 0;
|
|
1191
|
+
overflow-wrap: anywhere;
|
|
1125
1192
|
color: #28bb9c;
|
|
1126
1193
|
}
|
|
1127
1194
|
|
|
1128
1195
|
.login-agree-desc {
|
|
1196
|
+
min-width: 0;
|
|
1197
|
+
overflow-wrap: anywhere;
|
|
1129
1198
|
color: #9ca3af;
|
|
1130
|
-
white-space: nowrap;
|
|
1131
1199
|
}
|
|
1132
1200
|
}
|
|
1133
1201
|
</style>
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { onLoad } from '@dcloudio/uni-app'
|
|
3
3
|
import { useSettingStore } from '../../../stores'
|
|
4
4
|
import { ref } from 'vue'
|
|
5
|
+
import { useI18n } from '../../../locales'
|
|
5
6
|
|
|
6
7
|
const settingStore = useSettingStore()
|
|
8
|
+
const { t } = useI18n()
|
|
7
9
|
const content = ref('')
|
|
8
10
|
|
|
9
11
|
const protocolClassMap: Record<string, string> = {
|
|
@@ -46,14 +48,14 @@ const decorateProtocolContent = (html: string) => {
|
|
|
46
48
|
// 加载协议内容
|
|
47
49
|
const loadProtocol = async (type?: string) => {
|
|
48
50
|
const isPrivacy = type === 'privacy'
|
|
49
|
-
const title = isPrivacy ? '
|
|
51
|
+
const title = isPrivacy ? t('core.protocol.privacy') : t('core.protocol.service')
|
|
50
52
|
const key = isPrivacy ? 'privacyProtocol' : 'serviceProtocol'
|
|
51
53
|
|
|
52
54
|
try {
|
|
53
55
|
await settingStore.loadData()
|
|
54
56
|
const protocol = settingStore.getData(key)
|
|
55
57
|
if (!protocol) {
|
|
56
|
-
throw new Error(
|
|
58
|
+
throw new Error(t('core.protocol.not_configured', { title }))
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
await uni.setNavigationBarTitle({ title })
|
|
@@ -61,7 +63,7 @@ const loadProtocol = async (type?: string) => {
|
|
|
61
63
|
} catch (error) {
|
|
62
64
|
await uni.showToast({
|
|
63
65
|
icon: 'none',
|
|
64
|
-
title: error instanceof Error ? error.message : '
|
|
66
|
+
title: error instanceof Error ? error.message : t('core.protocol.load_failed'),
|
|
65
67
|
})
|
|
66
68
|
setTimeout(() => {
|
|
67
69
|
uni.navigateBack()
|
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { onLoad } from '@dcloudio/uni-app'
|
|
3
|
-
import { ref } from 'vue'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
4
|
import BootstrapStatus from '../../../components/BootstrapStatus.vue'
|
|
5
5
|
import { initializeAppNavigation, launchAppStatus, navigateAppRoute } from '../../../navigation'
|
|
6
6
|
import type { BootstrapViewKey } from '../../../module'
|
|
7
|
+
import { useI18n } from '../../../locales'
|
|
7
8
|
|
|
8
9
|
const state = ref<BootstrapViewKey>('BOOTSTRAP_LOADING')
|
|
9
10
|
const detail = ref('')
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
OFFLINE: '网络不可用',
|
|
15
|
-
CONFIG_ERROR: '导航配置无效',
|
|
16
|
-
PAGE_UNAVAILABLE: '页面暂不可用',
|
|
17
|
-
}
|
|
11
|
+
const { t } = useI18n()
|
|
12
|
+
const stateTitle = computed(() =>
|
|
13
|
+
state.value === 'BOOTSTRAP_LOADING' ? t('core.status.loading') : t(`core.status.${state.value}`),
|
|
14
|
+
)
|
|
18
15
|
|
|
19
|
-
onLoad(
|
|
16
|
+
onLoad((options) => {
|
|
20
17
|
state.value = (options?.state as BootstrapViewKey | undefined) ?? 'BOOTSTRAP_LOADING'
|
|
21
18
|
detail.value = options?.detail ? decodeURIComponent(options.detail) : ''
|
|
22
19
|
if (options?.bootstrap !== '1') return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
void initializeAppNavigation()
|
|
21
|
+
.then(() => {
|
|
22
|
+
navigateAppRoute(options?.route ? decodeURIComponent(options.route) : 'app/home', {
|
|
23
|
+
replace: true,
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
.catch((error) => {
|
|
27
|
+
launchAppStatus('CONFIG_ERROR', error instanceof Error ? error.message : String(error))
|
|
27
28
|
})
|
|
28
|
-
} catch (error) {
|
|
29
|
-
launchAppStatus('CONFIG_ERROR', error instanceof Error ? error.message : String(error))
|
|
30
|
-
}
|
|
31
29
|
})
|
|
32
30
|
</script>
|
|
33
31
|
|
|
34
32
|
<template>
|
|
35
|
-
<BootstrapStatus :title="
|
|
33
|
+
<BootstrapStatus :title="stateTitle" :detail="detail" />
|
|
36
34
|
</template>
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
<!-- #endif -->
|
|
18
18
|
|
|
19
19
|
<view v-if="showFallback" class="webview-empty">
|
|
20
|
-
<view class="webview-empty__title"
|
|
20
|
+
<view class="webview-empty__title">{{ t('core.webview.open_failed') }}</view>
|
|
21
21
|
<view class="webview-empty__desc">{{ emptyDesc }}</view>
|
|
22
22
|
</view>
|
|
23
23
|
|
|
24
24
|
<!-- #ifdef H5 -->
|
|
25
25
|
<!-- X-Frame-Options 拒绝嵌入时浏览器仍可能触发 iframe load,始终保留外部打开入口。 -->
|
|
26
26
|
<button v-if="isH5 && url && isIframeTimedOut" class="webview-open-button" @tap="openInBrowser">
|
|
27
|
-
|
|
27
|
+
{{ t('common.action.open_in_new_window') }}
|
|
28
28
|
</button>
|
|
29
29
|
<!-- #endif -->
|
|
30
30
|
</view>
|
|
@@ -33,20 +33,22 @@
|
|
|
33
33
|
<script setup lang="ts">
|
|
34
34
|
import { computed, ref } from 'vue'
|
|
35
35
|
import { onLoad } from '@dcloudio/uni-app'
|
|
36
|
+
import { useI18n } from '../../../locales'
|
|
36
37
|
|
|
37
38
|
const url = ref('')
|
|
38
39
|
const isH5 = ref(false)
|
|
39
40
|
const isIframeLoaded = ref(false)
|
|
40
41
|
const isIframeTimedOut = ref(false)
|
|
42
|
+
const { t } = useI18n()
|
|
41
43
|
|
|
42
44
|
const showFallback = computed(
|
|
43
45
|
() => !url.value || (isH5.value && isIframeTimedOut.value && !isIframeLoaded.value),
|
|
44
46
|
)
|
|
45
47
|
const emptyDesc = computed(() => {
|
|
46
48
|
if (!url.value) {
|
|
47
|
-
return '
|
|
49
|
+
return t('core.webview.invalid_url')
|
|
48
50
|
}
|
|
49
|
-
return '
|
|
51
|
+
return t('core.webview.embed_blocked')
|
|
50
52
|
})
|
|
51
53
|
|
|
52
54
|
onLoad((query) => {
|
package/src/vite.ts
CHANGED
|
@@ -582,6 +582,7 @@ function scanViews(root: string): Array<{ route: string; source: string }> {
|
|
|
582
582
|
function appendPage(manifest: PagesManifest, page: ScannedPage): void {
|
|
583
583
|
const [first, ...rest] = page.route.split('/')
|
|
584
584
|
if (!first.startsWith('pages') || first === 'pages') {
|
|
585
|
+
if (manifest.pages.some((item) => normalizeRoute(item.path) === page.route)) return
|
|
585
586
|
manifest.pages.push({ path: page.route, style: page.style })
|
|
586
587
|
return
|
|
587
588
|
}
|
|
@@ -591,22 +592,22 @@ function appendPage(manifest: PagesManifest, page: ScannedPage): void {
|
|
|
591
592
|
subPackage = { root: first, pages: [] }
|
|
592
593
|
manifest.subPackages.push(subPackage)
|
|
593
594
|
}
|
|
595
|
+
if (subPackage.pages.some((item) => normalizeRoute(`${first}/${item.path}`) === page.route))
|
|
596
|
+
return
|
|
594
597
|
subPackage.pages.push({ path: rest.join('/'), style: page.style })
|
|
595
598
|
}
|
|
596
599
|
|
|
597
600
|
function createPageWrapper(page: ScannedPage): string {
|
|
598
601
|
const source = page.source.replace(/\\/g, '/')
|
|
599
602
|
return `<script setup lang="ts">
|
|
600
|
-
import type { Component } from 'vue'
|
|
601
603
|
import KratosPage from ${JSON.stringify(source)}
|
|
602
604
|
import KratosTabBar from '@liujitcn/kratos-uni-app-core/components/KratosTabBar.vue'
|
|
603
605
|
|
|
604
606
|
defineOptions({ inheritAttrs: false })
|
|
605
|
-
const Page = KratosPage as Component
|
|
606
607
|
</script>
|
|
607
608
|
|
|
608
609
|
<template>
|
|
609
|
-
<
|
|
610
|
+
<KratosPage v-bind="$attrs" />
|
|
610
611
|
<KratosTabBar route=${JSON.stringify(page.route)} />
|
|
611
612
|
</template>
|
|
612
613
|
`
|