@liujitcn/kratos-taro-app-core 0.0.19 → 0.0.21
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/build.mjs +3 -3
- package/package.json +1 -1
- package/src/api/base/file.ts +9 -4
- package/src/api/base/language.ts +21 -0
- package/src/bootstrap.ts +15 -1
- package/src/components/BootstrapStatus.tsx +3 -1
- package/src/components/KratosPageFrame.tsx +3 -1
- package/src/index.ts +18 -0
- package/src/locales/README.md +15 -0
- package/src/locales/en-US.json +113 -0
- package/src/locales/es-ES.json +113 -0
- package/src/locales/fr-FR.json +113 -0
- package/src/locales/index.ts +211 -0
- package/src/locales/ja-JP.json +113 -0
- package/src/locales/ko-KR.json +113 -0
- package/src/locales/zh-CN.json +113 -0
- package/src/locales/zh-TW.json +113 -0
- package/src/module.ts +4 -0
- package/src/navigation.ts +48 -23
- package/src/pages.ts +3 -3
- package/src/rpc/base/v1/config.ts +2 -0
- package/src/rpc/base/v1/language.ts +37 -0
- package/src/stores/setting.ts +9 -4
- package/src/utils/file.ts +7 -2
- package/src/utils/http.ts +29 -10
- package/src/utils/passwordCrypto.ts +13 -12
- package/src/views/pages/index/index.tsx +13 -11
- package/src/views/pages/login/login.tsx +71 -48
- package/src/views/pages/login/protocal.tsx +5 -3
- package/src/views/pages/status/index.tsx +4 -10
- package/src/views/pages/webview/webview.tsx +6 -4
|
@@ -3,28 +3,30 @@ import { useLoad } from '@tarojs/taro'
|
|
|
3
3
|
import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
|
|
4
4
|
import { useSettingStore } from '../../../stores'
|
|
5
5
|
import './index.scss'
|
|
6
|
+
import { useI18n } from '../../../locales'
|
|
6
7
|
|
|
7
8
|
/** 应用首页。 */
|
|
8
9
|
export default function HomePage() {
|
|
10
|
+
const { t } = useI18n()
|
|
9
11
|
const settings = useSettingStore((state) => state.data)
|
|
10
12
|
const loadData = useSettingStore((state) => state.loadData)
|
|
11
|
-
const mainTitle = settings?.get('mainTitle') || '
|
|
12
|
-
const subTitle = settings?.get('subTitle') || '
|
|
13
|
+
const mainTitle = settings?.get('mainTitle') || t('core.home.mainTitle')
|
|
14
|
+
const subTitle = settings?.get('subTitle') || t('core.home.subTitle')
|
|
13
15
|
const appLogo = settings?.get('appLogo') || defaultLogo
|
|
14
16
|
useLoad(() => {
|
|
15
17
|
void loadData().catch(() => undefined)
|
|
16
18
|
})
|
|
17
19
|
|
|
18
20
|
const stack = [
|
|
19
|
-
['
|
|
20
|
-
['
|
|
21
|
-
['
|
|
22
|
-
['
|
|
21
|
+
[t('core.home.framework'), 'Taro + React 18'],
|
|
22
|
+
[t('core.home.language'), 'TypeScript'],
|
|
23
|
+
[t('core.home.stateManagement'), 'Zustand'],
|
|
24
|
+
[t('core.home.styleSolution'), 'Sass + px'],
|
|
23
25
|
]
|
|
24
26
|
const demos = [
|
|
25
|
-
['
|
|
26
|
-
['
|
|
27
|
-
['
|
|
27
|
+
[t('core.home.crossPlatform'), t('core.home.crossPlatformDescription')],
|
|
28
|
+
[t('core.home.accountCapability'), t('core.home.accountCapabilityDescription')],
|
|
29
|
+
[t('core.home.staticHome'), t('core.home.staticHomeDescription')],
|
|
28
30
|
]
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -42,7 +44,7 @@ export default function HomePage() {
|
|
|
42
44
|
</View>
|
|
43
45
|
|
|
44
46
|
<View className='home-section'>
|
|
45
|
-
<Text className='home-section__title'
|
|
47
|
+
<Text className='home-section__title'>{t('core.home.techStack')}</Text>
|
|
46
48
|
<View className='home-info-list'>
|
|
47
49
|
{stack.map(([label, value]) => (
|
|
48
50
|
<View className='home-info-row' key={label}>
|
|
@@ -54,7 +56,7 @@ export default function HomePage() {
|
|
|
54
56
|
</View>
|
|
55
57
|
|
|
56
58
|
<View className='home-section'>
|
|
57
|
-
<Text className='home-section__title'
|
|
59
|
+
<Text className='home-section__title'>{t('core.home.demoScope')}</Text>
|
|
58
60
|
<View className='home-demo-list'>
|
|
59
61
|
{demos.map(([title, description], index) => (
|
|
60
62
|
<View className='home-demo-item' key={title}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Button, Image, Input, Text, View } from '@tarojs/components'
|
|
1
|
+
import { Button, Image, Input, Picker, Text, View } from '@tarojs/components'
|
|
2
2
|
import Taro, { useLoad } from '@tarojs/taro'
|
|
3
|
-
import { useMemo, useRef, useState } from 'react'
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
4
|
import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
|
|
5
5
|
import { defLoginService } from '../../../api/base/login'
|
|
6
6
|
import { navigateAppRoute } from '../../../navigation'
|
|
@@ -14,6 +14,12 @@ import type {
|
|
|
14
14
|
BehaviorCaptchaPoint,
|
|
15
15
|
} from './components/types'
|
|
16
16
|
import './login.scss'
|
|
17
|
+
import {
|
|
18
|
+
t as translate,
|
|
19
|
+
useLocaleStore,
|
|
20
|
+
useI18n,
|
|
21
|
+
type SupportedLocale,
|
|
22
|
+
} from '../../../locales'
|
|
17
23
|
|
|
18
24
|
const behaviorCaptchaTypes = new Set(['slide', 'click', 'rotate'])
|
|
19
25
|
const wechatMiniProvider = 'wechatmini'
|
|
@@ -43,11 +49,9 @@ function isWechatUnboundError(error: unknown): boolean {
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
const reason = response.data?.reason ?? response.data?.error?.reason
|
|
46
|
-
const message = response.data?.message ?? response.data?.error?.message
|
|
47
52
|
return (
|
|
48
53
|
response.data?.binding_required === true ||
|
|
49
|
-
String(reason || '') === 'UNAUTHENTICATED'
|
|
50
|
-
message === '微信账号未绑定,请先绑定已有账号'
|
|
54
|
+
String(reason || '') === 'UNAUTHENTICATED'
|
|
51
55
|
)
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -62,7 +66,7 @@ async function resolveMiniCaptchaImage(payload: string, captchaId: string): Prom
|
|
|
62
66
|
data: payload.slice(commaIndex + 1),
|
|
63
67
|
encoding: 'base64',
|
|
64
68
|
success: () => resolve(),
|
|
65
|
-
fail: () => reject(new Error('
|
|
69
|
+
fail: () => reject(new Error(translate('core.login.captchaWriteFailed'))),
|
|
66
70
|
})
|
|
67
71
|
})
|
|
68
72
|
return filePath
|
|
@@ -70,6 +74,8 @@ async function resolveMiniCaptchaImage(payload: string, captchaId: string): Prom
|
|
|
70
74
|
|
|
71
75
|
/** 账号和微信授权登录页。 */
|
|
72
76
|
export default function LoginPage() {
|
|
77
|
+
const { locale, setLocale, t } = useI18n()
|
|
78
|
+
const languageOptions = useLocaleStore((state) => state.languageOptions)
|
|
73
79
|
const settings = useSettingStore((state) => state.data)
|
|
74
80
|
const loadSettings = useSettingStore((state) => state.loadData)
|
|
75
81
|
const userStore = useUserStore.getState()
|
|
@@ -91,8 +97,8 @@ export default function LoginPage() {
|
|
|
91
97
|
const [miniForm, setMiniForm] = useState(emptyLoginForm)
|
|
92
98
|
const [miniPassword, setMiniPassword] = useState('')
|
|
93
99
|
|
|
94
|
-
const mainTitle = settings?.get('mainTitle') || '
|
|
95
|
-
const subTitle = settings?.get('subTitle') || '
|
|
100
|
+
const mainTitle = settings?.get('mainTitle') || t('core.home.mainTitle')
|
|
101
|
+
const subTitle = settings?.get('subTitle') || t('core.login.defaultSubTitle')
|
|
96
102
|
const appLogo = settings?.get('appLogo') || defaultLogo
|
|
97
103
|
const showTenantCode = settings?.get('showTenantCode') !== 'false'
|
|
98
104
|
const configuredCaptchaType = settings?.get('captchaType') || ''
|
|
@@ -112,7 +118,7 @@ export default function LoginPage() {
|
|
|
112
118
|
verticalPadding: 0,
|
|
113
119
|
horizontalPadding: 0,
|
|
114
120
|
iconSize: 20,
|
|
115
|
-
title: '
|
|
121
|
+
title: t('core.login.behaviorRotate'),
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
return {
|
|
@@ -123,10 +129,10 @@ export default function LoginPage() {
|
|
|
123
129
|
showTheme: false,
|
|
124
130
|
verticalPadding: 0,
|
|
125
131
|
horizontalPadding: 0,
|
|
126
|
-
buttonText: '
|
|
132
|
+
buttonText: t('common.action.confirm'),
|
|
127
133
|
iconSize: 20,
|
|
128
134
|
dotSize: 20,
|
|
129
|
-
title: captchaType === 'click' ? '
|
|
135
|
+
title: captchaType === 'click' ? t('core.login.behaviorClick') : t('core.login.behaviorPuzzle'),
|
|
130
136
|
}
|
|
131
137
|
}, [
|
|
132
138
|
behaviorData.thumbHeight,
|
|
@@ -134,8 +140,13 @@ export default function LoginPage() {
|
|
|
134
140
|
behaviorHeight,
|
|
135
141
|
behaviorWidth,
|
|
136
142
|
captchaType,
|
|
143
|
+
locale,
|
|
137
144
|
]) as unknown as Record<string, string | number | boolean>
|
|
138
145
|
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
void Taro.setNavigationBarTitle({ title: t('common.action.login') })
|
|
148
|
+
}, [locale])
|
|
149
|
+
|
|
139
150
|
const updateForm = (key: keyof LoginRequest, value: string) => {
|
|
140
151
|
setForm((current) => ({ ...current, [key]: value }))
|
|
141
152
|
}
|
|
@@ -144,9 +155,9 @@ export default function LoginPage() {
|
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
const refreshCaptcha = async (requestedType = captchaType || configuredCaptchaType) => {
|
|
147
|
-
if (!requestedType) throw new Error('
|
|
158
|
+
if (!requestedType) throw new Error(t('core.login.captchaTypeMissing'))
|
|
148
159
|
const data = await defLoginService.Captcha({ type: requestedType })
|
|
149
|
-
if (!data.type) throw new Error('
|
|
160
|
+
if (!data.type) throw new Error(t('core.login.captchaTypeResponseMissing'))
|
|
150
161
|
setCaptchaType(data.type)
|
|
151
162
|
setForm((current) => ({ ...current, captcha_id: data.captcha_id, captcha_code: '' }))
|
|
152
163
|
if (!behaviorCaptchaTypes.has(data.type)) {
|
|
@@ -189,7 +200,7 @@ export default function LoginPage() {
|
|
|
189
200
|
captcha_code: '',
|
|
190
201
|
}))
|
|
191
202
|
} catch {
|
|
192
|
-
await Taro.showToast({ icon: 'none', title: '
|
|
203
|
+
await Taro.showToast({ icon: 'none', title: t('core.login.captchaLoadFailed') })
|
|
193
204
|
}
|
|
194
205
|
}
|
|
195
206
|
|
|
@@ -201,7 +212,7 @@ export default function LoginPage() {
|
|
|
201
212
|
await loadSettings()
|
|
202
213
|
const nextCaptchaType = useSettingStore.getState().getData('captchaType')
|
|
203
214
|
if (process.env.TARO_ENV === 'h5') {
|
|
204
|
-
if (!nextCaptchaType) throw new Error('
|
|
215
|
+
if (!nextCaptchaType) throw new Error(t('core.login.captchaTypeMissing'))
|
|
205
216
|
setCaptchaType(nextCaptchaType)
|
|
206
217
|
if (!behaviorCaptchaTypes.has(nextCaptchaType)) await refreshCaptcha(nextCaptchaType)
|
|
207
218
|
} else {
|
|
@@ -212,7 +223,7 @@ export default function LoginPage() {
|
|
|
212
223
|
} catch (error) {
|
|
213
224
|
await Taro.showToast({
|
|
214
225
|
icon: 'none',
|
|
215
|
-
title: error instanceof Error ? error.message : '
|
|
226
|
+
title: error instanceof Error ? error.message : t('core.protocol.loadFailed'),
|
|
216
227
|
})
|
|
217
228
|
settingsPromise.current = undefined
|
|
218
229
|
return false
|
|
@@ -229,17 +240,17 @@ export default function LoginPage() {
|
|
|
229
240
|
if (!(await loadLoginSettings())) return false
|
|
230
241
|
const currentSettings = useSettingStore.getState()
|
|
231
242
|
if (!currentSettings.getData('serviceProtocol') || !currentSettings.getData('privacyProtocol')) {
|
|
232
|
-
await Taro.showToast({ icon: 'none', title: '
|
|
243
|
+
await Taro.showToast({ icon: 'none', title: t('core.login.protocolMissing') })
|
|
233
244
|
return false
|
|
234
245
|
}
|
|
235
246
|
if (agreed) return true
|
|
236
247
|
setShake(true)
|
|
237
248
|
setTimeout(() => setShake(false), 500)
|
|
238
249
|
const result = await Taro.showModal({
|
|
239
|
-
title: '
|
|
240
|
-
content: '
|
|
241
|
-
confirmText: '
|
|
242
|
-
cancelText: '
|
|
250
|
+
title: t('common.title.notice'),
|
|
251
|
+
content: t('core.login.protocolPrompt'),
|
|
252
|
+
confirmText: t('common.action.confirm'),
|
|
253
|
+
cancelText: t('common.action.cancel'),
|
|
243
254
|
})
|
|
244
255
|
if (result.confirm) setAgreed(true)
|
|
245
256
|
return result.confirm
|
|
@@ -248,7 +259,7 @@ export default function LoginPage() {
|
|
|
248
259
|
/** 完成用户资料加载并恢复登录前页面。 */
|
|
249
260
|
const loginSuccess = async () => {
|
|
250
261
|
await userStore.getUserProfile()
|
|
251
|
-
await Taro.showToast({ icon: 'success', title: '
|
|
262
|
+
await Taro.showToast({ icon: 'success', title: t('core.login.loginSuccess') })
|
|
252
263
|
await new Promise<void>((resolve) => setTimeout(resolve, 500))
|
|
253
264
|
await restoreLoginRedirect()
|
|
254
265
|
}
|
|
@@ -256,10 +267,10 @@ export default function LoginPage() {
|
|
|
256
267
|
const validateLoginForm = async () => {
|
|
257
268
|
if (!(await loadLoginSettings())) return false
|
|
258
269
|
const validations: Array<[boolean, string]> = [
|
|
259
|
-
[!showTenantCode || Boolean(form.tenant_code), '
|
|
260
|
-
[Boolean(form.user_name), '
|
|
261
|
-
[Boolean(password), '
|
|
262
|
-
[isBehaviorCaptcha || Boolean(form.captcha_code), '
|
|
270
|
+
[!showTenantCode || Boolean(form.tenant_code), t('core.login.tenant')],
|
|
271
|
+
[Boolean(form.user_name), t('core.login.userName')],
|
|
272
|
+
[Boolean(password), t('core.login.password')],
|
|
273
|
+
[isBehaviorCaptcha || Boolean(form.captcha_code), t('core.login.captcha')],
|
|
263
274
|
]
|
|
264
275
|
const failed = validations.find(([valid]) => !valid)
|
|
265
276
|
if (failed) {
|
|
@@ -298,7 +309,7 @@ export default function LoginPage() {
|
|
|
298
309
|
} catch (error) {
|
|
299
310
|
await refreshCaptcha().catch(() => undefined)
|
|
300
311
|
if (error instanceof Error) {
|
|
301
|
-
await Taro.showToast({ icon: 'none', title: error.message || '
|
|
312
|
+
await Taro.showToast({ icon: 'none', title: error.message || t('core.login.loginFailed') })
|
|
302
313
|
}
|
|
303
314
|
} finally {
|
|
304
315
|
setLoading(false)
|
|
@@ -359,7 +370,7 @@ export default function LoginPage() {
|
|
|
359
370
|
setMiniBinding(true)
|
|
360
371
|
await refreshMiniCaptcha()
|
|
361
372
|
} else {
|
|
362
|
-
await Taro.showToast({ icon: 'none', title: '
|
|
373
|
+
await Taro.showToast({ icon: 'none', title: t('core.login.wechatFailed') })
|
|
363
374
|
}
|
|
364
375
|
} finally {
|
|
365
376
|
setLoading(false)
|
|
@@ -369,9 +380,9 @@ export default function LoginPage() {
|
|
|
369
380
|
const bindMiniAccount = async () => {
|
|
370
381
|
if (loading || !(await checkedAgreePrivacy())) return
|
|
371
382
|
const failed = [
|
|
372
|
-
[showTenantCode && !miniForm.tenant_code, '
|
|
373
|
-
[!miniForm.user_name || !miniPassword, '
|
|
374
|
-
[!miniForm.captcha_id || !miniForm.captcha_code, '
|
|
383
|
+
[showTenantCode && !miniForm.tenant_code, t('core.login.tenant')],
|
|
384
|
+
[!miniForm.user_name || !miniPassword, t('core.login.userNamePassword')],
|
|
385
|
+
[!miniForm.captcha_id || !miniForm.captcha_code, t('core.login.captcha')],
|
|
375
386
|
].find(([invalid]) => invalid)
|
|
376
387
|
if (failed) {
|
|
377
388
|
await Taro.showToast({ icon: 'none', title: String(failed[1]) })
|
|
@@ -403,7 +414,7 @@ export default function LoginPage() {
|
|
|
403
414
|
<View className={`login-tips${shake ? ' login-tips--shake' : ''}`}>
|
|
404
415
|
<View className='login-agreement' onClick={() => setAgreed((value) => !value)}>
|
|
405
416
|
<View className={`login-agree-icon${agreed ? ' checked' : ''}`} />
|
|
406
|
-
<Text className='login-agree-desc'
|
|
417
|
+
<Text className='login-agree-desc'>{t('core.login.agreePrefix')}</Text>
|
|
407
418
|
<Text
|
|
408
419
|
className='login-agree-link'
|
|
409
420
|
onClick={(event) => {
|
|
@@ -411,9 +422,9 @@ export default function LoginPage() {
|
|
|
411
422
|
navigateAppRoute('app/protocol/service')
|
|
412
423
|
}}
|
|
413
424
|
>
|
|
414
|
-
|
|
425
|
+
{t('core.login.service')}
|
|
415
426
|
</Text>
|
|
416
|
-
<Text className='login-agree-separator'
|
|
427
|
+
<Text className='login-agree-separator'>{t('core.login.agreeSeparator')}</Text>
|
|
417
428
|
<Text
|
|
418
429
|
className='login-agree-link'
|
|
419
430
|
onClick={(event) => {
|
|
@@ -421,7 +432,7 @@ export default function LoginPage() {
|
|
|
421
432
|
navigateAppRoute('app/protocol/privacy')
|
|
422
433
|
}}
|
|
423
434
|
>
|
|
424
|
-
|
|
435
|
+
{t('core.login.privacy')}
|
|
425
436
|
</Text>
|
|
426
437
|
</View>
|
|
427
438
|
</View>
|
|
@@ -429,6 +440,18 @@ export default function LoginPage() {
|
|
|
429
440
|
|
|
430
441
|
return (
|
|
431
442
|
<View className='login-page'>
|
|
443
|
+
<Picker
|
|
444
|
+
className='login-locale'
|
|
445
|
+
mode='selector'
|
|
446
|
+
range={languageOptions.map((item) => item.language_name)}
|
|
447
|
+
value={Math.max(0, languageOptions.findIndex((item) => item.language_code === locale))}
|
|
448
|
+
onChange={(event) => {
|
|
449
|
+
const nextLocale = languageOptions[Number(event.detail.value)]?.language_code as SupportedLocale | undefined
|
|
450
|
+
if (nextLocale) void setLocale(nextLocale)
|
|
451
|
+
}}
|
|
452
|
+
>
|
|
453
|
+
<View className='login-locale__value'>{t(`common.language.${locale}`)}</View>
|
|
454
|
+
</Picker>
|
|
432
455
|
<View className='login-hero'>
|
|
433
456
|
<View className='login-logo-shell'>
|
|
434
457
|
<Image className='login-logo' src={appLogo} />
|
|
@@ -442,24 +465,24 @@ export default function LoginPage() {
|
|
|
442
465
|
{process.env.TARO_ENV === 'h5' ? (
|
|
443
466
|
<View className='login-form'>
|
|
444
467
|
{showTenantCode ? (
|
|
445
|
-
<Input className='login-input' placeholder='
|
|
468
|
+
<Input className='login-input' placeholder={t('core.login.tenant')} value={form.tenant_code} onInput={(event) => updateForm('tenant_code', event.detail.value)} />
|
|
446
469
|
) : null}
|
|
447
|
-
<Input className='login-input' placeholder='
|
|
448
|
-
<Input className='login-input' password placeholder='
|
|
470
|
+
<Input className='login-input' placeholder={t('core.login.userNameMobile')} value={form.user_name} onInput={(event) => updateForm('user_name', event.detail.value)} />
|
|
471
|
+
<Input className='login-input' password placeholder={t('core.login.password')} value={password} onInput={(event) => setPassword(event.detail.value)} onConfirm={() => void onSubmit()} />
|
|
449
472
|
{!isBehaviorCaptcha ? (
|
|
450
473
|
<View className='captcha-row'>
|
|
451
|
-
<Input className='login-input captcha-input' placeholder='
|
|
474
|
+
<Input className='login-input captcha-input' placeholder={t('core.login.captcha')} value={form.captcha_code} onInput={(event) => updateForm('captcha_code', event.detail.value)} onConfirm={() => void onSubmit()} />
|
|
452
475
|
<View className='captcha-divider' />
|
|
453
476
|
<View className='captcha-trigger' onClick={() => void refreshCaptcha()}>
|
|
454
477
|
<Image className='captcha-image' src={captchaImage} mode='aspectFit' />
|
|
455
478
|
</View>
|
|
456
479
|
</View>
|
|
457
480
|
) : null}
|
|
458
|
-
<Button className='login-button login-button-primary' loading={loading} onClick={() => void onSubmit()}
|
|
481
|
+
<Button className='login-button login-button-primary' loading={loading} onClick={() => void onSubmit()}>{t('common.action.login')}</Button>
|
|
459
482
|
{behaviorVisible ? (
|
|
460
483
|
<View className='login-behavior-mask'>
|
|
461
484
|
<View className='login-behavior-panel'>
|
|
462
|
-
{behaviorLoading ? <View className='login-behavior-loading'
|
|
485
|
+
{behaviorLoading ? <View className='login-behavior-loading'>{t('core.login.behaviorLoading')}</View> : null}
|
|
463
486
|
<BehaviorCaptcha type={captchaType} data={behaviorData} config={behaviorConfig} onConfirm={(value, reset) => void verifyBehaviorCaptcha(value, reset)} onRefresh={() => void refreshCaptcha()} onClose={() => setBehaviorVisible(false)} />
|
|
464
487
|
</View>
|
|
465
488
|
</View>
|
|
@@ -467,20 +490,20 @@ export default function LoginPage() {
|
|
|
467
490
|
</View>
|
|
468
491
|
) : !miniBinding ? (
|
|
469
492
|
<Button className='login-button login-button-primary' loading={loading} onClick={() => void wxLogin()}>
|
|
470
|
-
<Text className='login-phone-icon'>☎</Text
|
|
493
|
+
<Text className='login-phone-icon'>☎</Text>{t('core.login.wechat')}
|
|
471
494
|
</Button>
|
|
472
495
|
) : (
|
|
473
496
|
<View className='login-form'>
|
|
474
|
-
<View className='login-bind-tip'
|
|
475
|
-
{showTenantCode ? <Input className='login-input' placeholder='
|
|
476
|
-
<Input className='login-input' placeholder='
|
|
477
|
-
<Input className='login-input' password placeholder='
|
|
497
|
+
<View className='login-bind-tip'>{t('core.login.wechatBindTip')}</View>
|
|
498
|
+
{showTenantCode ? <Input className='login-input' placeholder={t('core.login.tenant')} value={miniForm.tenant_code} onInput={(event) => updateMiniForm('tenant_code', event.detail.value)} /> : null}
|
|
499
|
+
<Input className='login-input' placeholder={t('core.login.userNameMobile')} value={miniForm.user_name} onInput={(event) => updateMiniForm('user_name', event.detail.value)} />
|
|
500
|
+
<Input className='login-input' password placeholder={t('core.login.password')} value={miniPassword} onInput={(event) => setMiniPassword(event.detail.value)} />
|
|
478
501
|
<View className='captcha-row'>
|
|
479
|
-
<Input className='login-input captcha-input' placeholder='
|
|
502
|
+
<Input className='login-input captcha-input' placeholder={t('core.login.captcha')} value={miniForm.captcha_code} onInput={(event) => updateMiniForm('captcha_code', event.detail.value)} />
|
|
480
503
|
<View className='captcha-divider' />
|
|
481
504
|
<View className='captcha-trigger' onClick={() => void refreshMiniCaptcha()}><Image className='captcha-image' src={miniCaptchaImage} mode='aspectFit' /></View>
|
|
482
505
|
</View>
|
|
483
|
-
<Button className='login-button login-button-primary' loading={loading} onClick={() => void bindMiniAccount()}
|
|
506
|
+
<Button className='login-button login-button-primary' loading={loading} onClick={() => void bindMiniAccount()}>{t('core.login.bindWechat')}</Button>
|
|
484
507
|
</View>
|
|
485
508
|
)}
|
|
486
509
|
{agreement}
|
|
@@ -2,6 +2,7 @@ import { RichText, View } from '@tarojs/components'
|
|
|
2
2
|
import Taro, { useLoad } from '@tarojs/taro'
|
|
3
3
|
import { useState } from 'react'
|
|
4
4
|
import { useSettingStore } from '../../../stores'
|
|
5
|
+
import { useI18n } from '../../../locales'
|
|
5
6
|
import './protocal.scss'
|
|
6
7
|
|
|
7
8
|
const protocolClassMap: Record<string, string> = {
|
|
@@ -39,25 +40,26 @@ function decorateProtocolContent(html: string): string {
|
|
|
39
40
|
|
|
40
41
|
/** 服务条款与隐私协议页。 */
|
|
41
42
|
export default function ProtocolPage() {
|
|
43
|
+
const { t } = useI18n()
|
|
42
44
|
const [content, setContent] = useState('')
|
|
43
45
|
|
|
44
46
|
useLoad((query) => {
|
|
45
47
|
const isPrivacy = query?.type === 'privacy'
|
|
46
|
-
const title = isPrivacy ? '
|
|
48
|
+
const title = isPrivacy ? t('core.protocol.privacy') : t('core.protocol.service')
|
|
47
49
|
const key = isPrivacy ? 'privacyProtocol' : 'serviceProtocol'
|
|
48
50
|
const settingStore = useSettingStore.getState()
|
|
49
51
|
void settingStore
|
|
50
52
|
.loadData()
|
|
51
53
|
.then(() => {
|
|
52
54
|
const protocol = settingStore.getData(key)
|
|
53
|
-
if (!protocol) throw new Error(
|
|
55
|
+
if (!protocol) throw new Error(t('core.protocol.notConfigured', { title }))
|
|
54
56
|
void Taro.setNavigationBarTitle({ title })
|
|
55
57
|
setContent(decorateProtocolContent(protocol))
|
|
56
58
|
})
|
|
57
59
|
.catch(async (error: unknown) => {
|
|
58
60
|
await Taro.showToast({
|
|
59
61
|
icon: 'none',
|
|
60
|
-
title: error instanceof Error ? error.message : '
|
|
62
|
+
title: error instanceof Error ? error.message : t('core.protocol.loadFailed'),
|
|
61
63
|
})
|
|
62
64
|
setTimeout(() => void Taro.navigateBack(), 300)
|
|
63
65
|
})
|
|
@@ -7,18 +7,11 @@ import {
|
|
|
7
7
|
navigateAppRoute,
|
|
8
8
|
} from '../../../navigation'
|
|
9
9
|
import type { BootstrapViewKey } from '../../../module'
|
|
10
|
-
|
|
11
|
-
const stateTitles: Record<BootstrapViewKey, string> = {
|
|
12
|
-
BOOTSTRAP_LOADING: '正在加载',
|
|
13
|
-
FORBIDDEN: '暂无访问权限',
|
|
14
|
-
NOT_FOUND: '页面不存在',
|
|
15
|
-
OFFLINE: '网络不可用',
|
|
16
|
-
CONFIG_ERROR: '导航配置无效',
|
|
17
|
-
PAGE_UNAVAILABLE: '页面暂不可用',
|
|
18
|
-
}
|
|
10
|
+
import { useI18n } from '../../../locales'
|
|
19
11
|
|
|
20
12
|
/** 启动与错误状态页。 */
|
|
21
13
|
export default function StatusPage() {
|
|
14
|
+
const { t } = useI18n()
|
|
22
15
|
const [state, setState] = useState<BootstrapViewKey>('BOOTSTRAP_LOADING')
|
|
23
16
|
const [detail, setDetail] = useState('')
|
|
24
17
|
|
|
@@ -38,5 +31,6 @@ export default function StatusPage() {
|
|
|
38
31
|
})
|
|
39
32
|
})
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
const title = state === 'BOOTSTRAP_LOADING' ? t('core.status.loading') : t(`core.status.${state}`)
|
|
35
|
+
return <BootstrapStatus title={title} detail={detail} />
|
|
42
36
|
}
|
|
@@ -2,9 +2,11 @@ import { Button, Text, View, WebView } from '@tarojs/components'
|
|
|
2
2
|
import Taro, { useLoad } from '@tarojs/taro'
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
4
|
import './webview.scss'
|
|
5
|
+
import { useI18n } from '../../../locales'
|
|
5
6
|
|
|
6
7
|
/** 统一外链承载页。 */
|
|
7
8
|
export default function WebViewPage() {
|
|
9
|
+
const { t } = useI18n()
|
|
8
10
|
const [url, setUrl] = useState('')
|
|
9
11
|
const [iframeLoaded, setIframeLoaded] = useState(false)
|
|
10
12
|
const [iframeTimedOut, setIframeTimedOut] = useState(false)
|
|
@@ -27,8 +29,8 @@ export default function WebViewPage() {
|
|
|
27
29
|
|
|
28
30
|
const showFallback = !url || (isH5 && iframeTimedOut && !iframeLoaded)
|
|
29
31
|
const emptyDescription = url
|
|
30
|
-
? '
|
|
31
|
-
: '
|
|
32
|
+
? t('core.webview.embedBlocked')
|
|
33
|
+
: t('core.webview.invalidUrl')
|
|
32
34
|
|
|
33
35
|
return (
|
|
34
36
|
<View className='webview-container'>
|
|
@@ -41,7 +43,7 @@ export default function WebViewPage() {
|
|
|
41
43
|
) : null}
|
|
42
44
|
{showFallback ? (
|
|
43
45
|
<View className='webview-empty'>
|
|
44
|
-
<Text className='webview-empty__title'
|
|
46
|
+
<Text className='webview-empty__title'>{t('core.webview.openFailed')}</Text>
|
|
45
47
|
<Text className='webview-empty__desc'>{emptyDescription}</Text>
|
|
46
48
|
</View>
|
|
47
49
|
) : null}
|
|
@@ -50,7 +52,7 @@ export default function WebViewPage() {
|
|
|
50
52
|
className='webview-open-button'
|
|
51
53
|
onClick={() => window.open(url, '_blank', 'noopener,noreferrer')}
|
|
52
54
|
>
|
|
53
|
-
|
|
55
|
+
{t('common.action.openInNewWindow')}
|
|
54
56
|
</Button>
|
|
55
57
|
) : null}
|
|
56
58
|
</View>
|