@liujitcn/kratos-taro-app-core 0.0.54 → 0.0.56
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/package.json +1 -1
- package/src/api/base/v1/config.ts +16 -1
- package/src/api/base/v1/file.ts +9 -8
- package/src/index.ts +2 -0
- package/src/locales/README.md +1 -1
- package/src/rpc/base/v1/config.ts +13 -1
- package/src/rpc/base/v1/file.ts +12 -0
- package/src/static/tabs/message_default.png +0 -0
- package/src/static/tabs/message_selected.png +0 -0
- package/src/stores/setting.ts +9 -1
- package/src/stores/user.ts +10 -0
- package/src/utils/file.ts +13 -5
- package/src/utils/index.ts +14 -3
- package/src/views/pages/index/index.tsx +11 -1
- package/src/views/pages/login/login.tsx +12 -3
package/README.md
CHANGED
|
@@ -40,6 +40,6 @@ pnpm test
|
|
|
40
40
|
pnpm check:exports
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
构建产物按模式与平台隔离:开发使用 `dist/dev/h5`、`dist/dev/mp-weixin`,生产默认使用 `dist/build/h5`、`dist/build/mp-weixin`。`KRATOS_TARO_OUTPUT_ROOT` 可覆盖默认目录;仓库宿主及 Kratos 配套项目的 H5 生产构建输出到 `backend/
|
|
43
|
+
构建产物按模式与平台隔离:开发使用 `dist/dev/h5`、`dist/dev/mp-weixin`,生产默认使用 `dist/build/h5`、`dist/build/mp-weixin`。`KRATOS_TARO_OUTPUT_ROOT` 可覆盖默认目录;仓库宿主及 Kratos 配套项目的 H5 生产构建输出到 `backend/web/taro-app`。微信开发者工具默认指向开发目录,发布时导入生产目录。
|
|
44
44
|
|
|
45
45
|
应用配置加载成功后,core 会将 `i18n_customs` 按 `locale + key` 覆盖已注册的本地文案,并刷新页面翻译。仅覆盖已有 key 的非空文案;未配置或已删除的覆盖项恢复本地默认值,未知语言和未知 key 忽略。切换语言时沿用配置重载流程。
|
package/package.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { http } from '../../../utils/http'
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ConfigService,
|
|
4
|
+
GetConfigRequest,
|
|
5
|
+
GetConfigResponse,
|
|
6
|
+
GetI18nCustomRequest,
|
|
7
|
+
GetI18nCustomResponse,
|
|
8
|
+
} from '../../../rpc/base/v1/config'
|
|
3
9
|
|
|
4
10
|
const CONFIG_URL = '/v1/base/config'
|
|
5
11
|
|
|
@@ -15,6 +21,15 @@ export class ConfigServiceImpl implements ConfigService {
|
|
|
15
21
|
header: { Authorization: 'no-auth' },
|
|
16
22
|
})
|
|
17
23
|
}
|
|
24
|
+
|
|
25
|
+
/** 获取当前租户的自定义国际化覆盖项。 */
|
|
26
|
+
GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse> {
|
|
27
|
+
return http<GetI18nCustomResponse>({
|
|
28
|
+
url: `${CONFIG_URL}/i18n-custom`,
|
|
29
|
+
method: 'GET',
|
|
30
|
+
data: request,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
export const defConfigService = new ConfigServiceImpl()
|
package/src/api/base/v1/file.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Taro from '@tarojs/taro'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
BaseFileAccessMode,
|
|
4
|
+
type DownloadFileRequest,
|
|
5
|
+
type FileInfo,
|
|
6
|
+
type MultiUploadFileResponse,
|
|
6
7
|
} from '../../../rpc/base/v1/file'
|
|
7
8
|
import { uploadFile, uploadFileList } from '../../../utils/file'
|
|
8
9
|
import { getLocaleRequestHeaders, t } from '../../../locales'
|
|
@@ -13,26 +14,26 @@ const FILE_URL = '/v1/base/file'
|
|
|
13
14
|
/** 文件服务。 */
|
|
14
15
|
export class FileServiceImpl {
|
|
15
16
|
/** 上传多个浏览器文件。 */
|
|
16
|
-
async MultiUploadFile(files: File[], fileType: string): Promise<MultiUploadFileResponse> {
|
|
17
|
+
async MultiUploadFile(files: File[], fileType: string, accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED): Promise<MultiUploadFileResponse> {
|
|
17
18
|
if (process.env.TARO_ENV !== 'h5') {
|
|
18
19
|
throw new Error(t('core.file.temporary_path_required'))
|
|
19
20
|
}
|
|
20
21
|
const paths = files.map((file) => URL.createObjectURL(file))
|
|
21
22
|
try {
|
|
22
|
-
return { files: await uploadFileList(fileType, paths) }
|
|
23
|
+
return { files: await uploadFileList(fileType, paths, accessMode) }
|
|
23
24
|
} finally {
|
|
24
25
|
paths.forEach((path) => URL.revokeObjectURL(path))
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/** 上传单个浏览器文件。 */
|
|
29
|
-
async UploadFile(file: File, fileType: string): Promise<FileInfo> {
|
|
30
|
+
async UploadFile(file: File, fileType: string, accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED): Promise<FileInfo> {
|
|
30
31
|
if (process.env.TARO_ENV !== 'h5') {
|
|
31
32
|
throw new Error(t('core.file.temporary_path_required'))
|
|
32
33
|
}
|
|
33
34
|
const path = URL.createObjectURL(file)
|
|
34
35
|
try {
|
|
35
|
-
return await uploadFile(fileType, path)
|
|
36
|
+
return await uploadFile(fileType, path, accessMode)
|
|
36
37
|
} finally {
|
|
37
38
|
URL.revokeObjectURL(path)
|
|
38
39
|
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export const coreModule = defineKratosTaroModule({
|
|
|
22
22
|
icons: {
|
|
23
23
|
HOME_DEFAULT: 'static/tabs/home_default.png',
|
|
24
24
|
HOME_SELECTED: 'static/tabs/home_selected.png',
|
|
25
|
+
MESSAGE_DEFAULT: 'static/tabs/message_default.png',
|
|
26
|
+
MESSAGE_SELECTED: 'static/tabs/message_selected.png',
|
|
25
27
|
USER_DEFAULT: 'static/tabs/user_default.png',
|
|
26
28
|
USER_SELECTED: 'static/tabs/user_selected.png',
|
|
27
29
|
},
|
package/src/locales/README.md
CHANGED
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
| `en-US.json` | 英文 Core 语言包。 | Taro 切换为英文时使用。 |
|
|
10
10
|
| `ja-JP.json` | 日文 Core 语言包。 | Taro 切换为日文时使用。 |
|
|
11
11
|
|
|
12
|
-
语言包是标准 JSON,不能在文件内直接写注释;公共键使用 `common.` 或 `core.` 命名空间。新增语言后执行仓库根目录的 `make i18n
|
|
12
|
+
语言包是标准 JSON,不能在文件内直接写注释;公共键使用 `common.` 或 `core.` 命名空间。新增语言后执行仓库根目录的 `make i18n`。语言切换选项固定显示后端 `base_language.native_name`,接口不可用时使用代码内置的原生名称;`common.language.*` 仅用于普通文案和语言迁移初始数据。新增语言的完整文件清单见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。
|
|
@@ -40,8 +40,18 @@ export interface GetConfigResponse {
|
|
|
40
40
|
configs: ConfigItem[];
|
|
41
41
|
/** AI 助手是否可用 */
|
|
42
42
|
ai_enabled: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 当前租户自定义国际化覆盖项查询条件。 */
|
|
46
|
+
export interface GetI18nCustomRequest {
|
|
47
|
+
/** 位置:枚举【BaseConfigSite】 */
|
|
48
|
+
site: BaseConfigSite;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 当前租户自定义国际化覆盖项查询结果。 */
|
|
52
|
+
export interface GetI18nCustomResponse {
|
|
43
53
|
/** 全部语言的自定义国际化覆盖项 */
|
|
44
|
-
|
|
54
|
+
items: I18nCustomItem[];
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
/** 全部语言的自定义国际化覆盖项。 */
|
|
@@ -58,4 +68,6 @@ export interface I18nCustomItem {
|
|
|
58
68
|
export interface ConfigService {
|
|
59
69
|
/** 获取系统配置 */
|
|
60
70
|
GetConfig(request: GetConfigRequest): Promise<GetConfigResponse>;
|
|
71
|
+
/** 获取当前租户的自定义国际化覆盖项 */
|
|
72
|
+
GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse>;
|
|
61
73
|
}
|
package/src/rpc/base/v1/file.ts
CHANGED
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
/* eslint-disable */
|
|
8
8
|
import type { BytesValue } from "../../google/protobuf/wrappers";
|
|
9
9
|
|
|
10
|
+
/** 文件访问方式。 */
|
|
11
|
+
export enum BaseFileAccessMode {
|
|
12
|
+
/** BASE_FILE_ACCESS_MODE_UNSPECIFIED - 未指定文件访问方式,服务端按授权访问处理。 */
|
|
13
|
+
BASE_FILE_ACCESS_MODE_UNSPECIFIED = 0,
|
|
14
|
+
/** BASE_FILE_ACCESS_MODE_PUBLIC - 允许匿名公开访问。 */
|
|
15
|
+
BASE_FILE_ACCESS_MODE_PUBLIC = 1,
|
|
16
|
+
/** BASE_FILE_ACCESS_MODE_AUTHORIZED - 必须携带有效访问令牌。 */
|
|
17
|
+
BASE_FILE_ACCESS_MODE_AUTHORIZED = 2,
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
/** 批量上传文件请求参数 */
|
|
11
21
|
export interface MultiUploadFileRequest {
|
|
12
22
|
/** 文件名 */
|
|
@@ -35,6 +45,8 @@ export interface UploadFileInfo {
|
|
|
35
45
|
path: string;
|
|
36
46
|
/** 二进制内容将通过 base64 编码传输 */
|
|
37
47
|
content: Uint8Array;
|
|
48
|
+
/** 文件访问方式,未指定时默认需要访问令牌 */
|
|
49
|
+
access_mode: BaseFileAccessMode;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
/** 下载文件请求参数 */
|
|
Binary file
|
|
Binary file
|
package/src/stores/setting.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface SettingStoreState {
|
|
|
15
15
|
aiEnabled: boolean
|
|
16
16
|
getData: (key: string) => string | undefined
|
|
17
17
|
loadData: () => Promise<void>
|
|
18
|
+
loadI18nCustom: () => Promise<void>
|
|
19
|
+
resetI18nCustom: () => void
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
let loading: Promise<void> | undefined
|
|
@@ -36,7 +38,6 @@ export const useSettingStore = create<SettingStoreState>((set, get) => ({
|
|
|
36
38
|
set({ aiEnabled: false })
|
|
37
39
|
return
|
|
38
40
|
}
|
|
39
|
-
applyCustomLocaleMessages(response.i18n_customs ?? [])
|
|
40
41
|
const nextData = new Map(response.configs.map((item) => [item.key, item.value]))
|
|
41
42
|
const missing = REQUIRED_APP_CONFIGS.filter(({ key }) => !nextData.get(key))
|
|
42
43
|
if (missing.length) {
|
|
@@ -54,4 +55,11 @@ export const useSettingStore = create<SettingStoreState>((set, get) => ({
|
|
|
54
55
|
loading = undefined
|
|
55
56
|
}
|
|
56
57
|
},
|
|
58
|
+
async loadI18nCustom() {
|
|
59
|
+
const response = await defConfigService.GetI18nCustom({ site: BaseConfigSite.BASE_CONFIG_SITE_APP })
|
|
60
|
+
applyCustomLocaleMessages(response.items ?? [])
|
|
61
|
+
},
|
|
62
|
+
resetI18nCustom() {
|
|
63
|
+
applyCustomLocaleMessages([])
|
|
64
|
+
},
|
|
57
65
|
}))
|
package/src/stores/user.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
setTokenExpiresIn,
|
|
22
22
|
} from '../utils/auth'
|
|
23
23
|
import { AUTH_SILENT_LOGOUT_EVENT } from '../utils/http'
|
|
24
|
+
import { useSettingStore } from './setting'
|
|
24
25
|
|
|
25
26
|
const USER_STORAGE_KEY = 'user'
|
|
26
27
|
|
|
@@ -94,6 +95,12 @@ export const useUserStore = create<UserStoreState>((set, get) => ({
|
|
|
94
95
|
setToken(`${data.token_type} ${data.access_token}`)
|
|
95
96
|
setRefreshToken(data.refresh_token)
|
|
96
97
|
setTokenExpiresIn(data.expires_in)
|
|
98
|
+
const settingStore = useSettingStore.getState()
|
|
99
|
+
try {
|
|
100
|
+
await settingStore.loadI18nCustom()
|
|
101
|
+
} catch {
|
|
102
|
+
settingStore.resetI18nCustom()
|
|
103
|
+
}
|
|
97
104
|
await runUserStoreExtensions('onLogin')
|
|
98
105
|
},
|
|
99
106
|
async login(request) {
|
|
@@ -137,12 +144,14 @@ export const useUserStore = create<UserStoreState>((set, get) => ({
|
|
|
137
144
|
clearToken()
|
|
138
145
|
persistUserInfo()
|
|
139
146
|
set({ userInfo: undefined })
|
|
147
|
+
useSettingStore.getState().resetI18nCustom()
|
|
140
148
|
await runUserStoreExtensions('onLogout')
|
|
141
149
|
},
|
|
142
150
|
silentLogout() {
|
|
143
151
|
clearToken()
|
|
144
152
|
persistUserInfo()
|
|
145
153
|
set({ userInfo: undefined })
|
|
154
|
+
useSettingStore.getState().resetI18nCustom()
|
|
146
155
|
void runUserStoreExtensions('onSilentLogout')
|
|
147
156
|
},
|
|
148
157
|
ensureAuthenticated() {
|
|
@@ -159,6 +168,7 @@ export function startUserStoreEventBridge(): void {
|
|
|
159
168
|
Taro.eventCenter.on(AUTH_SILENT_LOGOUT_EVENT, () => {
|
|
160
169
|
persistUserInfo()
|
|
161
170
|
useUserStore.setState({ userInfo: undefined })
|
|
171
|
+
useSettingStore.getState().resetI18nCustom()
|
|
162
172
|
void runUserStoreExtensions('onSilentLogout')
|
|
163
173
|
})
|
|
164
174
|
}
|
package/src/utils/file.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import Taro from '@tarojs/taro'
|
|
2
|
-
import type
|
|
2
|
+
import { BaseFileAccessMode, type FileInfo, type MultiUploadFileResponse } from '../rpc/base/v1/file'
|
|
3
3
|
import { getLocaleRequestHeaders, t } from '../locales'
|
|
4
4
|
import { formatSrc } from './index'
|
|
5
5
|
import { getRequestAccessToken, requestBaseURL } from './http'
|
|
6
6
|
|
|
7
7
|
/** 上传单个文件。 */
|
|
8
|
-
export async function uploadFile(
|
|
8
|
+
export async function uploadFile(
|
|
9
|
+
fileType: string,
|
|
10
|
+
filePath: string,
|
|
11
|
+
accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
|
|
12
|
+
): Promise<FileInfo> {
|
|
9
13
|
const token = await getRequestAccessToken()
|
|
10
14
|
const response = await Taro.uploadFile({
|
|
11
15
|
url: `${requestBaseURL}/v1/base/file`,
|
|
12
16
|
name: 'file',
|
|
13
17
|
filePath,
|
|
14
|
-
formData: { fileType },
|
|
18
|
+
formData: { fileType, accessMode: String(accessMode) },
|
|
15
19
|
header: {
|
|
16
20
|
...getLocaleRequestHeaders(),
|
|
17
21
|
'source-client': 'miniapp',
|
|
@@ -23,8 +27,12 @@ export async function uploadFile(fileType: string, filePath: string): Promise<Fi
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/** 并发上传文件列表。 */
|
|
26
|
-
export async function uploadFileList(
|
|
27
|
-
|
|
30
|
+
export async function uploadFileList(
|
|
31
|
+
fileType: string,
|
|
32
|
+
filePaths: string[],
|
|
33
|
+
accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
|
|
34
|
+
): Promise<FileInfo[]> {
|
|
35
|
+
const results = await Promise.allSettled(filePaths.map((filePath) => uploadFile(fileType, filePath, accessMode)))
|
|
28
36
|
return results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []))
|
|
29
37
|
}
|
|
30
38
|
|
package/src/utils/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
const configuredStaticUrl =
|
|
1
|
+
const configuredStaticUrl =
|
|
2
|
+
process.env.VITE_APP_STATIC_URL || process.env.VITE_APP_API_URL || ''
|
|
3
|
+
|
|
4
|
+
/** 将后端数据文件地址切换到当前构建配置的静态资源域名。 */
|
|
5
|
+
function rewriteDataFileOrigin(src: string, staticOrigin: string): string {
|
|
6
|
+
if (!staticOrigin) return src
|
|
7
|
+
const match = src.match(/^https?:\/\/[^/]+(\/data(?:\/|$).*)$/)
|
|
8
|
+
return match ? `${staticOrigin}${match[1]}` : src
|
|
9
|
+
}
|
|
2
10
|
|
|
3
11
|
/** 日期格式化函数。 */
|
|
4
12
|
export function formatDate(date: Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
|
|
@@ -24,10 +32,13 @@ export function formatPrice(price: number): string {
|
|
|
24
32
|
|
|
25
33
|
/** 格式化后端静态资源地址。 */
|
|
26
34
|
export function formatSrc(src: string): string {
|
|
27
|
-
if (!src || /^https?:\/\//.test(src)) return src
|
|
28
35
|
const browserOrigin =
|
|
29
|
-
typeof window !== 'undefined' && window.location?.origin
|
|
36
|
+
process.env.TARO_ENV === 'h5' && typeof window !== 'undefined' && window.location?.origin
|
|
37
|
+
? window.location.origin
|
|
38
|
+
: ''
|
|
30
39
|
const staticOrigin = browserOrigin || configuredStaticUrl.replace(/\/$/, '')
|
|
40
|
+
if (!src) return src
|
|
41
|
+
if (/^https?:\/\//.test(src)) return rewriteDataFileOrigin(src, staticOrigin)
|
|
31
42
|
if (!staticOrigin) return src
|
|
32
43
|
return src.startsWith('/')
|
|
33
44
|
? `${staticOrigin}${src}`
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Image, Text, View } from '@tarojs/components'
|
|
2
2
|
import { useLoad } from '@tarojs/taro'
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
3
4
|
import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
|
|
4
5
|
import { useSettingStore } from '../../../stores'
|
|
6
|
+
import { formatSrc } from '../../../utils'
|
|
5
7
|
import './index.scss'
|
|
6
8
|
import { useI18n } from '../../../locales'
|
|
7
9
|
|
|
@@ -12,7 +14,14 @@ export default function HomePage() {
|
|
|
12
14
|
const loadData = useSettingStore((state) => state.loadData)
|
|
13
15
|
const mainTitle = settings?.get('mainTitle') || t('core.home.main_title')
|
|
14
16
|
const subTitle = settings?.get('subTitle') || t('core.home.sub_title')
|
|
15
|
-
const
|
|
17
|
+
const configuredAppLogo = settings?.get('appLogo')
|
|
18
|
+
const configuredLogoUrl = configuredAppLogo ? formatSrc(configuredAppLogo) : defaultLogo
|
|
19
|
+
const [appLogo, setAppLogo] = useState(configuredLogoUrl)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setAppLogo(configuredLogoUrl)
|
|
23
|
+
}, [configuredLogoUrl])
|
|
24
|
+
|
|
16
25
|
useLoad(() => {
|
|
17
26
|
void loadData().catch(() => undefined)
|
|
18
27
|
})
|
|
@@ -36,6 +45,7 @@ export default function HomePage() {
|
|
|
36
45
|
className='home-logo'
|
|
37
46
|
src={appLogo}
|
|
38
47
|
mode='aspectFit'
|
|
48
|
+
onError={() => setAppLogo(defaultLogo)}
|
|
39
49
|
/>
|
|
40
50
|
<View className='home-hero__copy'>
|
|
41
51
|
<Text className='home-title'>{mainTitle}</Text>
|
|
@@ -7,6 +7,7 @@ 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
9
|
import { useSettingStore, useUserStore } from '../../../stores'
|
|
10
|
+
import { formatSrc } from '../../../utils'
|
|
10
11
|
import { restoreLoginRedirect } from '../../../utils/navigation'
|
|
11
12
|
import { encryptPassword, PASSWORD_CRYPTO_SCENE } from '../../../utils/passwordCrypto'
|
|
12
13
|
import { createWebAuthnCredential, getWebAuthnAssertion } from '../../../utils/webauthn'
|
|
@@ -124,7 +125,9 @@ export default function LoginPage() {
|
|
|
124
125
|
languageOptions.find((item) => item.language_code === locale)?.native_name || locale
|
|
125
126
|
const mainTitle = settings?.get('mainTitle') || t('core.home.main_title')
|
|
126
127
|
const subTitle = settings?.get('subTitle') || t('core.login.default_sub_title')
|
|
127
|
-
const
|
|
128
|
+
const configuredAppLogo = settings?.get('appLogo')
|
|
129
|
+
const configuredLogoUrl = configuredAppLogo ? formatSrc(configuredAppLogo) : defaultLogo
|
|
130
|
+
const [appLogo, setAppLogo] = useState(configuredLogoUrl)
|
|
128
131
|
const showTenantCode = settings?.get('showTenantCode') !== 'false'
|
|
129
132
|
const configuredCaptchaType = settings?.get('captchaType') || ''
|
|
130
133
|
const isBehaviorCaptcha = behaviorCaptchaTypes.has(captchaType)
|
|
@@ -169,6 +172,10 @@ export default function LoginPage() {
|
|
|
169
172
|
t,
|
|
170
173
|
]) as unknown as Record<string, string | number | boolean>
|
|
171
174
|
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
setAppLogo(configuredLogoUrl)
|
|
177
|
+
}, [configuredLogoUrl])
|
|
178
|
+
|
|
172
179
|
useEffect(() => {
|
|
173
180
|
void Taro.setNavigationBarTitle({ title: t('common.action.login') })
|
|
174
181
|
}, [locale, t])
|
|
@@ -601,7 +608,7 @@ export default function LoginPage() {
|
|
|
601
608
|
</Picker>
|
|
602
609
|
<View className='login-hero'>
|
|
603
610
|
<View className='login-logo-shell'>
|
|
604
|
-
<Image className='login-logo' src={appLogo} />
|
|
611
|
+
<Image className='login-logo' src={appLogo} onError={() => setAppLogo(defaultLogo)} />
|
|
605
612
|
</View>
|
|
606
613
|
<View className='login-hero-copy'>
|
|
607
614
|
<Text className='login-title'>{mainTitle}</Text>
|
|
@@ -754,7 +761,9 @@ export default function LoginPage() {
|
|
|
754
761
|
<Checkbox
|
|
755
762
|
value='remember'
|
|
756
763
|
checked={rememberMfaDevice}
|
|
757
|
-
onChange={(event) =>
|
|
764
|
+
onChange={(event) =>
|
|
765
|
+
setRememberMfaDevice(event.detail.value.includes('remember'))
|
|
766
|
+
}
|
|
758
767
|
/>
|
|
759
768
|
<Text>{t('core.login.mfa_remember_device', { days: mfaRememberDays })}</Text>
|
|
760
769
|
</View>
|