@liujitcn/kratos-taro-app-core 0.0.78 → 0.0.80
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 +4 -2
- package/package.json +1 -1
- package/src/components/BootstrapStatus.scss +3 -3
- package/src/components/BootstrapStatus.tsx +3 -1
- package/src/components/KratosTabBar.scss +32 -13
- package/src/components/KratosTabBar.tsx +34 -3
- package/src/components/MfaRecoveryCodesDialog.scss +11 -10
- package/src/components/MfaSetupPanel.scss +4 -4
- package/src/components/PasswordVerifyDialog.scss +21 -21
- package/src/navigation.ts +6 -3
- package/src/pages.ts +4 -2
- package/src/rpc/base/v1/config.ts +1 -1
- package/src/rpc/base/v1/file.ts +1 -1
- package/src/rpc/base/v1/language.ts +1 -1
- package/src/rpc/base/v1/login.ts +1 -1
- package/src/rpc/base/v1/mcp.ts +1 -1
- package/src/rpc/base/v1/mfa.ts +1 -1
- package/src/rpc/base/v1/oauth.ts +1 -1
- package/src/rpc/base/v1/secret_crypto.ts +1 -1
- package/src/rpc/base/v1/sse.ts +1 -1
- package/src/rpc/common/v1/common.ts +1 -1
- package/src/rpc/common/v1/types.ts +1 -1
- package/src/rpc/google/protobuf/empty.ts +1 -1
- package/src/rpc/google/protobuf/struct.ts +2 -2
- package/src/rpc/google/protobuf/wrappers.ts +1 -1
- package/src/rpc/system/app/v1/auth.ts +1 -1
- package/src/rpc/system/app/v1/base_area.ts +1 -1
- package/src/rpc/system/app/v1/base_dict.ts +1 -1
- package/src/rpc/system/app/v1/base_menu.ts +1 -1
- package/src/rpc/system/common/v1/common.ts +1 -1
- package/src/stores/index.ts +1 -0
- package/src/stores/theme.ts +108 -0
- package/src/styles/base.scss +90 -5
- package/src/styles/components.scss +765 -0
- package/src/utils/http.ts +3 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/navigation.ts +12 -0
- package/src/views/pages/index/index.scss +69 -73
- package/src/views/pages/index/index.tsx +50 -34
- package/src/views/pages/login/login.scss +109 -142
- package/src/views/pages/login/login.tsx +142 -82
- package/src/views/pages/login/protocal.scss +14 -14
- package/src/views/pages/login/protocal.tsx +16 -3
- package/src/views/pages/webview/webview.scss +6 -5
- package/src/views/pages/webview/webview.tsx +3 -1
|
@@ -6,9 +6,9 @@ import { defLoginService } from '../../../api/base/v1/login'
|
|
|
6
6
|
import { defMfaService } from '../../../api/base/v1/mfa'
|
|
7
7
|
import { navigateAppRoute } from '../../../navigation'
|
|
8
8
|
import { LoginStatus, type LoginRequest, type LoginResponse } from '../../../rpc/base/v1/login'
|
|
9
|
-
import { useSettingStore, useUserStore } from '../../../stores'
|
|
9
|
+
import { useSettingStore, useThemeStore, useUserStore } from '../../../stores'
|
|
10
10
|
import { formatSrc } from '../../../utils'
|
|
11
|
-
import { restoreLoginRedirect } from '../../../utils/navigation'
|
|
11
|
+
import { homeTabPage, restoreLoginRedirect } from '../../../utils/navigation'
|
|
12
12
|
import { encryptPassword } from '../../../utils/secretCrypto'
|
|
13
13
|
import { createWebAuthnCredential, getWebAuthnAssertion } from '../../../utils/webauthn'
|
|
14
14
|
import MfaRecoveryCodesDialog from '../../../components/MfaRecoveryCodesDialog'
|
|
@@ -21,6 +21,12 @@ import { t as translate, useLocaleStore, useI18n, type SupportedLocale } from '.
|
|
|
21
21
|
const behaviorCaptchaTypes = new Set(['slide', 'click', 'rotate'])
|
|
22
22
|
const wechatMiniProvider = 'wechatmini'
|
|
23
23
|
|
|
24
|
+
/** 参考管理端 View、Hide 图标视觉的本端 SVG 路径。 */
|
|
25
|
+
const passwordIconPaths = {
|
|
26
|
+
view: 'M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352m0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288m0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448m0 64a160.19 160.19 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160',
|
|
27
|
+
hide: 'M876.8 156.8c0-9.6-3.2-16-9.6-22.4s-12.8-9.6-22.4-9.6-16 3.2-22.4 9.6L736 220.8c-64-32-137.6-51.2-224-60.8-160 16-288 73.6-377.6 176S0 496 0 512s48 73.6 134.4 176c22.4 25.6 44.8 48 73.6 67.2l-86.4 89.6c-6.4 6.4-9.6 12.8-9.6 22.4s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6 16-3.2 22.4-9.6l704-710.4c3.2-6.4 6.4-12.8 6.4-22.4m-646.4 528Q115.2 579.2 76.8 512q43.2-72 153.6-172.8C304 272 400 230.4 512 224c64 3.2 124.8 19.2 176 44.8l-54.4 54.4C598.4 300.8 560 288 512 288c-64 0-115.2 22.4-160 64s-64 96-64 160c0 48 12.8 89.6 35.2 124.8L256 707.2c-9.6-6.4-19.2-16-25.6-22.4m140.8-96Q352 555.2 352 512c0-44.8 16-83.2 48-112s67.2-48 112-48c28.8 0 54.4 6.4 73.6 19.2zM889.599 336c-12.8-16-28.8-28.8-41.6-41.6l-48 48c73.6 67.2 124.8 124.8 150.4 169.6q-43.2 72-153.6 172.8c-73.6 67.2-172.8 108.8-284.8 115.2-51.2-3.2-99.2-12.8-140.8-28.8l-48 48c57.6 22.4 118.4 38.4 188.8 44.8 160-16 288-73.6 377.6-176S1024 528 1024 512s-48.001-73.6-134.401-176M511.998 672c-12.8 0-25.6-3.2-38.4-6.4l-51.2 51.2c28.8 12.8 57.6 19.2 89.6 19.2 64 0 115.2-22.4 160-64 41.6-41.6 64-96 64-160 0-32-6.4-64-19.2-89.6l-51.2 51.2c3.2 12.8 6.4 25.6 6.4 38.4 0 44.8-16 83.2-48 112s-67.2 48-112 48',
|
|
28
|
+
} as const
|
|
29
|
+
|
|
24
30
|
type BehaviorPayload = BehaviorCaptchaData & {
|
|
25
31
|
type?: string
|
|
26
32
|
width?: number
|
|
@@ -76,6 +82,14 @@ export default function LoginPage() {
|
|
|
76
82
|
const { locale, setLocale, t } = useI18n()
|
|
77
83
|
const languageOptions = useLocaleStore((state) => state.languageOptions)
|
|
78
84
|
const settings = useSettingStore((state) => state.data)
|
|
85
|
+
const themeStore = useThemeStore()
|
|
86
|
+
|
|
87
|
+
/** 返回上一页;登录页可能是冷启动首页,无上级页面时回主页。 */
|
|
88
|
+
const goBack = () => {
|
|
89
|
+
Taro.navigateBack({
|
|
90
|
+
fail: () => void Taro.reLaunch({ url: homeTabPage }),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
79
93
|
const loadSettings = useSettingStore((state) => state.loadData)
|
|
80
94
|
const userStore = useUserStore.getState()
|
|
81
95
|
const [agreed, setAgreed] = useState(false)
|
|
@@ -85,6 +99,7 @@ export default function LoginPage() {
|
|
|
85
99
|
const settingsPromise = useRef<Promise<boolean>>()
|
|
86
100
|
const [form, setForm] = useState<LoginRequest>(emptyLoginForm)
|
|
87
101
|
const [password, setPassword] = useState('')
|
|
102
|
+
const [passwordVisible, setPasswordVisible] = useState(false)
|
|
88
103
|
const [captchaImage, setCaptchaImage] = useState('')
|
|
89
104
|
const [captchaType, setCaptchaType] = useState('')
|
|
90
105
|
const [behaviorVisible, setBehaviorVisible] = useState(false)
|
|
@@ -95,6 +110,7 @@ export default function LoginPage() {
|
|
|
95
110
|
const [miniCaptchaImage, setMiniCaptchaImage] = useState('')
|
|
96
111
|
const [miniForm, setMiniForm] = useState(emptyLoginForm)
|
|
97
112
|
const [miniPassword, setMiniPassword] = useState('')
|
|
113
|
+
const [miniPasswordVisible, setMiniPasswordVisible] = useState(false)
|
|
98
114
|
const [mfaVisible, setMfaVisible] = useState(false)
|
|
99
115
|
const [mfaLoading, setMfaLoading] = useState(false)
|
|
100
116
|
const [mfaChallengeId, setMfaChallengeId] = useState('')
|
|
@@ -122,6 +138,13 @@ export default function LoginPage() {
|
|
|
122
138
|
const configuredLogoUrl = configuredAppLogo ? formatSrc(configuredAppLogo) : defaultLogo
|
|
123
139
|
const [appLogo, setAppLogo] = useState(configuredLogoUrl)
|
|
124
140
|
const showTenantCode = settings?.get('showTenantCode') !== 'false'
|
|
141
|
+
/** 按管理端 View、Hide 视觉生成本端密码可见性图标。 */
|
|
142
|
+
const passwordIconSrc = (visible: boolean) => {
|
|
143
|
+
const color = themeStore.theme === 'light' ? '#5a7083' : '#8aa0b4'
|
|
144
|
+
const path = visible ? passwordIconPaths.hide : passwordIconPaths.view
|
|
145
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="${color}"><path d="${path}"/></svg>`
|
|
146
|
+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
|
|
147
|
+
}
|
|
125
148
|
const configuredCaptchaType = settings?.get('captchaType') || ''
|
|
126
149
|
const isBehaviorCaptcha = behaviorCaptchaTypes.has(captchaType)
|
|
127
150
|
const behaviorWidth = Math.round(
|
|
@@ -169,10 +192,6 @@ export default function LoginPage() {
|
|
|
169
192
|
setAppLogo(configuredLogoUrl)
|
|
170
193
|
}, [configuredLogoUrl])
|
|
171
194
|
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
void Taro.setNavigationBarTitle({ title: t('common.action.login') })
|
|
174
|
-
}, [locale, t])
|
|
175
|
-
|
|
176
195
|
useEffect(() => {
|
|
177
196
|
return () => {
|
|
178
197
|
if (shakeTimerRef.current) clearTimeout(shakeTimerRef.current)
|
|
@@ -288,6 +307,8 @@ export default function LoginPage() {
|
|
|
288
307
|
content: t('core.login.protocol_prompt'),
|
|
289
308
|
confirmText: t('common.action.confirm'),
|
|
290
309
|
cancelText: t('common.action.cancel'),
|
|
310
|
+
confirmColor: themeStore.theme === 'light' ? '#039e8c' : '#28f0d4',
|
|
311
|
+
cancelColor: themeStore.theme === 'light' ? '#5a7083' : '#8aa0b4',
|
|
291
312
|
})
|
|
292
313
|
if (result.confirm) setAgreed(true)
|
|
293
314
|
return result.confirm
|
|
@@ -558,11 +579,11 @@ export default function LoginPage() {
|
|
|
558
579
|
|
|
559
580
|
const agreement = (
|
|
560
581
|
<View className={`login-tips${shake ? ' login-tips--shake' : ''}`}>
|
|
561
|
-
<View className='login-agreement' onClick={() => setAgreed((value) => !value)}>
|
|
562
|
-
<View className={`
|
|
582
|
+
<View className='k-lg-agree login-agreement' onClick={() => setAgreed((value) => !value)}>
|
|
583
|
+
<View className={`k-lg-chk${agreed ? '' : ' k-lg-chk--off'}`}>✓</View>
|
|
563
584
|
<Text className='login-agree-desc'>{t('core.login.agree_prefix')}</Text>
|
|
564
585
|
<Text
|
|
565
|
-
className='login-agree-link'
|
|
586
|
+
className='k-lg-link login-agree-link'
|
|
566
587
|
onClick={(event) => {
|
|
567
588
|
event.stopPropagation()
|
|
568
589
|
navigateAppRoute('app/protocol/service')
|
|
@@ -572,7 +593,7 @@ export default function LoginPage() {
|
|
|
572
593
|
</Text>
|
|
573
594
|
<Text className='login-agree-separator'>{t('core.login.agree_separator')}</Text>
|
|
574
595
|
<Text
|
|
575
|
-
className='login-agree-link'
|
|
596
|
+
className='k-lg-link login-agree-link'
|
|
576
597
|
onClick={(event) => {
|
|
577
598
|
event.stopPropagation()
|
|
578
599
|
navigateAppRoute('app/protocol/privacy')
|
|
@@ -585,78 +606,100 @@ export default function LoginPage() {
|
|
|
585
606
|
)
|
|
586
607
|
|
|
587
608
|
return (
|
|
588
|
-
<View className=
|
|
589
|
-
<
|
|
590
|
-
className='login-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
609
|
+
<View className={`login-page ${themeStore.theme === 'light' ? 'theme-light' : 'theme-dark'}`}>
|
|
610
|
+
<View className='login-header'>
|
|
611
|
+
<View className='k-ic login-back' aria-hidden onClick={goBack}>
|
|
612
|
+
‹
|
|
613
|
+
</View>
|
|
614
|
+
<Picker
|
|
615
|
+
className='login-locale'
|
|
616
|
+
mode='selector'
|
|
617
|
+
range={languageOptions.map((item) => item.native_name)}
|
|
618
|
+
value={Math.max(
|
|
619
|
+
0,
|
|
620
|
+
languageOptions.findIndex((item) => item.language_code === locale),
|
|
621
|
+
)}
|
|
622
|
+
onChange={(event) => {
|
|
623
|
+
const nextLocale = languageOptions[Number(event.detail.value)]?.language_code as
|
|
624
|
+
| SupportedLocale
|
|
625
|
+
| undefined
|
|
626
|
+
if (nextLocale) void setLocale(nextLocale)
|
|
627
|
+
}}
|
|
628
|
+
>
|
|
629
|
+
<View className='k-lg-lang login-locale__value'>{currentLanguageName}</View>
|
|
630
|
+
</Picker>
|
|
631
|
+
</View>
|
|
606
632
|
<View className='login-hero'>
|
|
607
|
-
<View className='login-logo-shell'>
|
|
633
|
+
<View className='k-lg-logo login-logo-shell'>
|
|
608
634
|
<Image className='login-logo' src={appLogo} onError={() => setAppLogo(defaultLogo)} />
|
|
609
635
|
</View>
|
|
610
636
|
<View className='login-hero-copy'>
|
|
611
|
-
<Text className='login-title'>{mainTitle}</Text>
|
|
612
|
-
<Text className='login-subtitle'>{subTitle}</Text>
|
|
637
|
+
<Text className='k-lg-title login-title'>{mainTitle}</Text>
|
|
638
|
+
<Text className='k-lg-sub login-subtitle'>{subTitle}</Text>
|
|
613
639
|
</View>
|
|
614
640
|
</View>
|
|
615
|
-
<View className='login-panel'>
|
|
641
|
+
<View className='k-glass k-glass--lit login-panel'>
|
|
642
|
+
<View className='k-cnr k-cnr--tl' aria-hidden />
|
|
643
|
+
<View className='k-cnr k-cnr--tr' aria-hidden />
|
|
644
|
+
<View className='k-cnr k-cnr--bl' aria-hidden />
|
|
645
|
+
<View className='k-cnr k-cnr--br' aria-hidden />
|
|
616
646
|
{process.env.TARO_ENV === 'h5' ? (
|
|
617
647
|
<View className='login-form'>
|
|
618
648
|
{showTenantCode ? (
|
|
649
|
+
<View className='k-lg-field'>
|
|
650
|
+
<Input
|
|
651
|
+
className='k-lg-field__input'
|
|
652
|
+
placeholder={t('core.login.tenant')}
|
|
653
|
+
value={form.tenant_code}
|
|
654
|
+
onInput={(event) => updateForm('tenant_code', event.detail.value)}
|
|
655
|
+
/>
|
|
656
|
+
</View>
|
|
657
|
+
) : null}
|
|
658
|
+
<View className='k-lg-field'>
|
|
619
659
|
<Input
|
|
620
|
-
className='
|
|
621
|
-
placeholder={t('core.login.
|
|
622
|
-
value={form.
|
|
623
|
-
onInput={(event) => updateForm('
|
|
660
|
+
className='k-lg-field__input'
|
|
661
|
+
placeholder={t('core.login.user_name_mobile')}
|
|
662
|
+
value={form.user_name}
|
|
663
|
+
onInput={(event) => updateForm('user_name', event.detail.value)}
|
|
624
664
|
/>
|
|
625
|
-
|
|
626
|
-
<
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
665
|
+
</View>
|
|
666
|
+
<View className='k-lg-field'>
|
|
667
|
+
<Input
|
|
668
|
+
className='k-lg-field__input'
|
|
669
|
+
password={!passwordVisible}
|
|
670
|
+
placeholder={t('core.login.password')}
|
|
671
|
+
value={password}
|
|
672
|
+
onInput={(event) => setPassword(event.detail.value)}
|
|
673
|
+
onConfirm={() => void onSubmit()}
|
|
674
|
+
/>
|
|
675
|
+
<View
|
|
676
|
+
className='password-toggle'
|
|
677
|
+
aria-label={passwordVisible ? '隐藏密码' : '显示密码'}
|
|
678
|
+
onClick={() => setPasswordVisible((visible) => !visible)}
|
|
679
|
+
>
|
|
680
|
+
<Image
|
|
681
|
+
className='password-toggle-icon'
|
|
682
|
+
src={passwordIconSrc(passwordVisible)}
|
|
683
|
+
mode='aspectFit'
|
|
684
|
+
/>
|
|
685
|
+
</View>
|
|
686
|
+
</View>
|
|
640
687
|
{!isBehaviorCaptcha ? (
|
|
641
|
-
<View className='
|
|
688
|
+
<View className='k-lg-field'>
|
|
642
689
|
<Input
|
|
643
|
-
className='
|
|
690
|
+
className='k-lg-field__input'
|
|
644
691
|
placeholder={t('core.login.captcha')}
|
|
645
692
|
value={form.captcha_code}
|
|
646
693
|
onInput={(event) => updateForm('captcha_code', event.detail.value)}
|
|
647
694
|
onConfirm={() => void onSubmit()}
|
|
648
695
|
/>
|
|
649
696
|
<View className='captcha-divider' />
|
|
650
|
-
<View className='captcha-trigger' onClick={() => void refreshCaptcha()}>
|
|
697
|
+
<View className='k-lg-cap captcha-trigger' onClick={() => void refreshCaptcha()}>
|
|
651
698
|
<Image className='captcha-image' src={captchaImage} mode='aspectFit' />
|
|
652
699
|
</View>
|
|
653
700
|
</View>
|
|
654
701
|
) : null}
|
|
655
|
-
<Button
|
|
656
|
-
className='login-button login-button-primary'
|
|
657
|
-
loading={loading}
|
|
658
|
-
onClick={() => void onSubmit()}
|
|
659
|
-
>
|
|
702
|
+
<Button className='k-lg-btn k-lg-btn--pri' loading={loading} onClick={() => void onSubmit()}>
|
|
660
703
|
{t('common.action.login')}
|
|
661
704
|
</Button>
|
|
662
705
|
{behaviorVisible ? (
|
|
@@ -681,7 +724,7 @@ export default function LoginPage() {
|
|
|
681
724
|
</View>
|
|
682
725
|
) : !miniBinding ? (
|
|
683
726
|
<Button
|
|
684
|
-
className='
|
|
727
|
+
className='k-lg-btn k-lg-btn--wx'
|
|
685
728
|
loading={loading}
|
|
686
729
|
onClick={() => void wxLogin()}
|
|
687
730
|
>
|
|
@@ -692,40 +735,57 @@ export default function LoginPage() {
|
|
|
692
735
|
<View className='login-form'>
|
|
693
736
|
<View className='login-bind-tip'>{t('core.login.wechat_bind_tip')}</View>
|
|
694
737
|
{showTenantCode ? (
|
|
738
|
+
<View className='k-lg-field'>
|
|
739
|
+
<Input
|
|
740
|
+
className='k-lg-field__input'
|
|
741
|
+
placeholder={t('core.login.tenant')}
|
|
742
|
+
value={miniForm.tenant_code}
|
|
743
|
+
onInput={(event) => updateMiniForm('tenant_code', event.detail.value)}
|
|
744
|
+
/>
|
|
745
|
+
</View>
|
|
746
|
+
) : null}
|
|
747
|
+
<View className='k-lg-field'>
|
|
695
748
|
<Input
|
|
696
|
-
className='
|
|
697
|
-
placeholder={t('core.login.
|
|
698
|
-
value={miniForm.
|
|
699
|
-
onInput={(event) => updateMiniForm('
|
|
749
|
+
className='k-lg-field__input'
|
|
750
|
+
placeholder={t('core.login.user_name_mobile')}
|
|
751
|
+
value={miniForm.user_name}
|
|
752
|
+
onInput={(event) => updateMiniForm('user_name', event.detail.value)}
|
|
700
753
|
/>
|
|
701
|
-
|
|
702
|
-
<
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
754
|
+
</View>
|
|
755
|
+
<View className='k-lg-field'>
|
|
756
|
+
<Input
|
|
757
|
+
className='k-lg-field__input'
|
|
758
|
+
password={!miniPasswordVisible}
|
|
759
|
+
placeholder={t('core.login.password')}
|
|
760
|
+
value={miniPassword}
|
|
761
|
+
onInput={(event) => setMiniPassword(event.detail.value)}
|
|
762
|
+
/>
|
|
763
|
+
<View
|
|
764
|
+
className='password-toggle'
|
|
765
|
+
aria-label={miniPasswordVisible ? '隐藏密码' : '显示密码'}
|
|
766
|
+
onClick={() => setMiniPasswordVisible((visible) => !visible)}
|
|
767
|
+
>
|
|
768
|
+
<Image
|
|
769
|
+
className='password-toggle-icon'
|
|
770
|
+
src={passwordIconSrc(miniPasswordVisible)}
|
|
771
|
+
mode='aspectFit'
|
|
772
|
+
/>
|
|
773
|
+
</View>
|
|
774
|
+
</View>
|
|
775
|
+
<View className='k-lg-field'>
|
|
716
776
|
<Input
|
|
717
|
-
className='
|
|
777
|
+
className='k-lg-field__input'
|
|
718
778
|
placeholder={t('core.login.captcha')}
|
|
719
779
|
value={miniForm.captcha_code}
|
|
720
780
|
onInput={(event) => updateMiniForm('captcha_code', event.detail.value)}
|
|
721
781
|
/>
|
|
722
782
|
<View className='captcha-divider' />
|
|
723
|
-
<View className='captcha-trigger' onClick={() => void refreshMiniCaptcha()}>
|
|
783
|
+
<View className='k-lg-cap captcha-trigger' onClick={() => void refreshMiniCaptcha()}>
|
|
724
784
|
<Image className='captcha-image' src={miniCaptchaImage} mode='aspectFit' />
|
|
725
785
|
</View>
|
|
726
786
|
</View>
|
|
727
787
|
<Button
|
|
728
|
-
className='
|
|
788
|
+
className='k-lg-btn k-lg-btn--pri'
|
|
729
789
|
loading={loading}
|
|
730
790
|
onClick={() => void bindMiniAccount()}
|
|
731
791
|
>
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
min-height: 100vh;
|
|
3
3
|
padding: 24px;
|
|
4
4
|
box-sizing: border-box;
|
|
5
|
-
background
|
|
5
|
+
background: var(--kratos-bg-gradient);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.protocol-card {
|
|
9
9
|
overflow: hidden;
|
|
10
10
|
padding: 40px 32px 56px;
|
|
11
|
-
border:
|
|
11
|
+
border: 1PX solid var(--kratos-color-border);
|
|
12
12
|
border-radius: 24px;
|
|
13
|
-
background-color:
|
|
14
|
-
box-shadow:
|
|
13
|
+
background-color: var(--kratos-color-surface);
|
|
14
|
+
box-shadow: var(--kratos-glow-soft);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.protocol-content {
|
|
18
18
|
display: block;
|
|
19
|
-
color:
|
|
19
|
+
color: var(--kratos-color-text-muted);
|
|
20
20
|
font-size: 28px;
|
|
21
21
|
line-height: 1.85;
|
|
22
22
|
word-break: break-word;
|
|
23
23
|
|
|
24
24
|
.protocol-title {
|
|
25
25
|
margin: 0 0 28px;
|
|
26
|
-
color:
|
|
26
|
+
color: var(--kratos-color-text);
|
|
27
27
|
font-size: 42px;
|
|
28
28
|
font-weight: 700;
|
|
29
29
|
line-height: 1.35;
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
.protocol-section-title {
|
|
33
33
|
margin: 38px 0 14px;
|
|
34
34
|
padding-left: 16px;
|
|
35
|
-
border-left: 6px solid
|
|
36
|
-
color:
|
|
35
|
+
border-left: 6px solid var(--kratos-color-primary);
|
|
36
|
+
color: var(--kratos-color-text);
|
|
37
37
|
font-size: 31px;
|
|
38
38
|
font-weight: 600;
|
|
39
39
|
line-height: 1.45;
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
.protocol-paragraph {
|
|
43
43
|
margin: 0 0 22px;
|
|
44
|
-
color:
|
|
44
|
+
color: var(--kratos-color-text-muted);
|
|
45
45
|
font-size: 28px;
|
|
46
46
|
line-height: 1.85;
|
|
47
47
|
}
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
.protocol-emphasis {
|
|
61
|
-
color:
|
|
61
|
+
color: var(--kratos-color-text);
|
|
62
62
|
font-weight: 600;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
.protocol-link {
|
|
66
|
-
color:
|
|
66
|
+
color: var(--kratos-color-primary);
|
|
67
67
|
text-decoration: underline;
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
.protocol-quote {
|
|
78
78
|
margin: 24px 0;
|
|
79
79
|
padding: 18px 22px;
|
|
80
|
-
border-left: 6px solid
|
|
81
|
-
color:
|
|
82
|
-
background-color:
|
|
80
|
+
border-left: 6px solid var(--kratos-color-primary-edge);
|
|
81
|
+
color: var(--kratos-color-text-muted);
|
|
82
|
+
background-color: var(--kratos-color-primary-soft);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { RichText, View } from '@tarojs/components'
|
|
2
2
|
import Taro, { useLoad } from '@tarojs/taro'
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import { useSettingStore } from '../../../stores'
|
|
4
|
+
import { useSettingStore, useThemeStore } from '../../../stores'
|
|
5
5
|
import { useI18n } from '../../../locales'
|
|
6
|
+
import { homeTabPage } from '../../../utils/navigation'
|
|
6
7
|
import './protocal.scss'
|
|
7
8
|
|
|
8
9
|
const protocolClassMap: Record<string, string> = {
|
|
@@ -41,11 +42,14 @@ function decorateProtocolContent(html: string): string {
|
|
|
41
42
|
/** 服务条款与隐私协议页。 */
|
|
42
43
|
export default function ProtocolPage() {
|
|
43
44
|
const { t } = useI18n()
|
|
45
|
+
const themeStore = useThemeStore()
|
|
44
46
|
const [content, setContent] = useState('')
|
|
47
|
+
const [pageTitle, setPageTitle] = useState(t('core.navigation.protocol'))
|
|
45
48
|
|
|
46
49
|
useLoad((query) => {
|
|
47
50
|
const isPrivacy = query?.type === 'privacy'
|
|
48
51
|
const title = isPrivacy ? t('core.protocol.privacy') : t('core.protocol.service')
|
|
52
|
+
setPageTitle(title)
|
|
49
53
|
const key = isPrivacy ? 'privacyProtocol' : 'serviceProtocol'
|
|
50
54
|
const settingStore = useSettingStore.getState()
|
|
51
55
|
void settingStore
|
|
@@ -53,7 +57,6 @@ export default function ProtocolPage() {
|
|
|
53
57
|
.then(() => {
|
|
54
58
|
const protocol = settingStore.getData(key)
|
|
55
59
|
if (!protocol) throw new Error(t('core.protocol.not_configured', { title }))
|
|
56
|
-
void Taro.setNavigationBarTitle({ title })
|
|
57
60
|
setContent(decorateProtocolContent(protocol))
|
|
58
61
|
})
|
|
59
62
|
.catch(async (error: unknown) => {
|
|
@@ -65,8 +68,18 @@ export default function ProtocolPage() {
|
|
|
65
68
|
})
|
|
66
69
|
})
|
|
67
70
|
|
|
71
|
+
/** 返回来源页,页面栈为空时回到首页。 */
|
|
72
|
+
const onBack = () => {
|
|
73
|
+
void Taro.navigateBack({ fail: () => void Taro.reLaunch({ url: homeTabPage }) })
|
|
74
|
+
}
|
|
75
|
+
|
|
68
76
|
return (
|
|
69
|
-
<View className=
|
|
77
|
+
<View className={`protocol-page ${themeStore.theme === 'light' ? 'theme-light' : 'theme-dark'}`}>
|
|
78
|
+
<View className={`k-page-nav ${process.env.TARO_ENV === 'weapp' ? 'k-page-nav--weapp' : ''}`}>
|
|
79
|
+
<View className='k-page-nav__back' aria-label={t('common.action.back')} onClick={onBack} />
|
|
80
|
+
<View className='k-page-nav__title'>{pageTitle}</View>
|
|
81
|
+
<View className='k-page-nav__side' aria-hidden />
|
|
82
|
+
</View>
|
|
70
83
|
<View className='protocol-card'>
|
|
71
84
|
<RichText className='protocol-content' nodes={content} />
|
|
72
85
|
</View>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
position: relative;
|
|
3
3
|
flex: 1;
|
|
4
4
|
height: 100vh;
|
|
5
|
+
background: var(--kratos-bg-gradient);
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
.webview-empty {
|
|
@@ -13,19 +14,19 @@
|
|
|
13
14
|
justify-content: center;
|
|
14
15
|
padding: 48px;
|
|
15
16
|
text-align: center;
|
|
16
|
-
background
|
|
17
|
+
background: var(--kratos-bg-gradient);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
.webview-empty__title {
|
|
20
21
|
font-size: 32px;
|
|
21
22
|
font-weight: 600;
|
|
22
|
-
color:
|
|
23
|
+
color: var(--kratos-color-text);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
.webview-empty__desc {
|
|
26
27
|
margin-top: 16px;
|
|
27
28
|
font-size: 26px;
|
|
28
|
-
color:
|
|
29
|
+
color: var(--kratos-color-text-muted);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
.webview-open-button {
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
height: 72px;
|
|
38
39
|
line-height: 72px;
|
|
39
40
|
border-radius: 72px;
|
|
40
|
-
background-color
|
|
41
|
-
color:
|
|
41
|
+
background: linear-gradient(135deg, var(--kratos-color-primary-deep), var(--kratos-color-primary));
|
|
42
|
+
color: var(--kratos-color-on-primary);
|
|
42
43
|
font-size: 26px;
|
|
43
44
|
}
|
|
@@ -3,6 +3,7 @@ import Taro, { useLoad } from '@tarojs/taro'
|
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
4
|
import './webview.scss'
|
|
5
5
|
import { useI18n } from '../../../locales'
|
|
6
|
+
import { useThemeStore } from '../../../stores'
|
|
6
7
|
|
|
7
8
|
/** 只放行 http(s) 外链,其它协议按非法地址处理。 */
|
|
8
9
|
const isAllowedUrl = (value: string) => /^https?:\/\//i.test(value)
|
|
@@ -18,6 +19,7 @@ const safeDecodeURIComponent = (value: string) => {
|
|
|
18
19
|
/** 统一外链承载页。 */
|
|
19
20
|
export default function WebViewPage() {
|
|
20
21
|
const { t } = useI18n()
|
|
22
|
+
const themeStore = useThemeStore()
|
|
21
23
|
const [url, setUrl] = useState('')
|
|
22
24
|
const [iframeLoaded, setIframeLoaded] = useState(false)
|
|
23
25
|
const [iframeTimedOut, setIframeTimedOut] = useState(false)
|
|
@@ -44,7 +46,7 @@ export default function WebViewPage() {
|
|
|
44
46
|
: t('core.webview.invalid_url')
|
|
45
47
|
|
|
46
48
|
return (
|
|
47
|
-
<View className=
|
|
49
|
+
<View className={`webview-container ${themeStore.theme === 'light' ? 'theme-light' : 'theme-dark'}`}>
|
|
48
50
|
{url ? (
|
|
49
51
|
<WebView src={url} onLoad={() => setIframeLoaded(true)} />
|
|
50
52
|
) : null}
|