@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
package/src/module.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { corePages } from './pages'
|
|
2
|
+
import type { LocaleMessages, SupportedLocale } from './locales'
|
|
3
|
+
|
|
4
|
+
// Core 语言包由同步脚本生成并注册,供 uni-app 公共页面、请求工具和基础组件使用。
|
|
5
|
+
import { LOCALE_MESSAGES } from './locales/generated'
|
|
2
6
|
|
|
3
7
|
/** bootstrap 可见状态键。 */
|
|
4
8
|
export type BootstrapViewKey =
|
|
@@ -21,6 +25,8 @@ export interface KratosAppModule {
|
|
|
21
25
|
pages: Record<string, KratosAppPageConfig>
|
|
22
26
|
views: Record<string, string>
|
|
23
27
|
icons?: Record<string, string>
|
|
28
|
+
/** 模块按语言区域贡献的扁平语言包。 */
|
|
29
|
+
messages?: Partial<Record<SupportedLocale, LocaleMessages>>
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
/** 声明 uni-app 模块。 */
|
|
@@ -82,4 +88,5 @@ export const coreModule = defineKratosAppModule({
|
|
|
82
88
|
USER_DEFAULT: 'static/tabs/user_default.png',
|
|
83
89
|
USER_SELECTED: 'static/tabs/user_selected.png',
|
|
84
90
|
},
|
|
91
|
+
messages: LOCALE_MESSAGES,
|
|
85
92
|
})
|
package/src/navigation.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { navigateToLogin } from './utils/navigation'
|
|
|
5
5
|
import { resolveStaticView } from './module'
|
|
6
6
|
import { matchLogicalPath, parseLogicalQuery } from './navigation-pattern.mjs'
|
|
7
7
|
import { buildMenuTree } from './navigation-tree.mjs'
|
|
8
|
+
import { getCurrentLocale, t } from './locales'
|
|
8
9
|
|
|
9
10
|
/** 移动菜单访问模式。 */
|
|
10
11
|
export type AppMenuAccess = 'PUBLIC' | 'GUEST_ONLY' | 'AUTHENTICATED'
|
|
@@ -45,14 +46,14 @@ const ANONYMOUS_CACHE_KEY = 'kratos-uni-app:navigation:anonymous'
|
|
|
45
46
|
const AUTHENTICATED_CACHE_KEY = 'kratos-uni-app:navigation:authenticated'
|
|
46
47
|
/** 固定移动端菜单根目录编号。 */
|
|
47
48
|
export const APP_MENU_ROOT_ID = 999
|
|
48
|
-
const
|
|
49
|
+
const createDefaultMenus = (): AppMenu[] => [
|
|
49
50
|
{
|
|
50
51
|
id: 99901,
|
|
51
52
|
parentId: APP_MENU_ROOT_ID,
|
|
52
53
|
name: 'AppHome',
|
|
53
54
|
path: 'app/home',
|
|
54
55
|
viewKey: 'HOME',
|
|
55
|
-
title: '
|
|
56
|
+
title: t('core.navigation.home'),
|
|
56
57
|
access: 'PUBLIC',
|
|
57
58
|
inTabBar: true,
|
|
58
59
|
icon: 'HOME_DEFAULT',
|
|
@@ -64,7 +65,7 @@ const defaultMenus: AppMenu[] = [
|
|
|
64
65
|
name: 'AppMy',
|
|
65
66
|
path: 'app/my',
|
|
66
67
|
viewKey: 'PROFILE_HOME',
|
|
67
|
-
title: '
|
|
68
|
+
title: t('core.navigation.my'),
|
|
68
69
|
access: 'PUBLIC',
|
|
69
70
|
inTabBar: true,
|
|
70
71
|
icon: 'USER_DEFAULT',
|
|
@@ -76,7 +77,7 @@ const defaultMenus: AppMenu[] = [
|
|
|
76
77
|
name: 'AppLogin',
|
|
77
78
|
path: 'app/login',
|
|
78
79
|
viewKey: 'LOGIN',
|
|
79
|
-
title: '
|
|
80
|
+
title: t('common.action.login'),
|
|
80
81
|
access: 'GUEST_ONLY',
|
|
81
82
|
inTabBar: false,
|
|
82
83
|
},
|
|
@@ -86,7 +87,7 @@ const defaultMenus: AppMenu[] = [
|
|
|
86
87
|
name: 'AppProtocol',
|
|
87
88
|
path: 'app/protocol/:type',
|
|
88
89
|
viewKey: 'PROTOCOL',
|
|
89
|
-
title: '
|
|
90
|
+
title: t('core.navigation.protocol'),
|
|
90
91
|
access: 'PUBLIC',
|
|
91
92
|
inTabBar: false,
|
|
92
93
|
},
|
|
@@ -96,7 +97,7 @@ const defaultMenus: AppMenu[] = [
|
|
|
96
97
|
name: 'AppProfile',
|
|
97
98
|
path: 'app/profile',
|
|
98
99
|
viewKey: 'PROFILE',
|
|
99
|
-
title: '
|
|
100
|
+
title: t('system.profile.title'),
|
|
100
101
|
access: 'AUTHENTICATED',
|
|
101
102
|
inTabBar: false,
|
|
102
103
|
},
|
|
@@ -106,7 +107,7 @@ const defaultMenus: AppMenu[] = [
|
|
|
106
107
|
name: 'AppSettings',
|
|
107
108
|
path: 'app/settings',
|
|
108
109
|
viewKey: 'SETTINGS',
|
|
109
|
-
title: '
|
|
110
|
+
title: t('system.settings.title'),
|
|
110
111
|
access: 'AUTHENTICATED',
|
|
111
112
|
inTabBar: false,
|
|
112
113
|
},
|
|
@@ -116,7 +117,7 @@ const defaultMenus: AppMenu[] = [
|
|
|
116
117
|
name: 'AppAi',
|
|
117
118
|
path: 'app/ai',
|
|
118
119
|
viewKey: 'AI',
|
|
119
|
-
title: '
|
|
120
|
+
title: t('system.ai.chat_title'),
|
|
120
121
|
access: 'AUTHENTICATED',
|
|
121
122
|
inTabBar: false,
|
|
122
123
|
},
|
|
@@ -132,8 +133,9 @@ const defaultMenus: AppMenu[] = [
|
|
|
132
133
|
},
|
|
133
134
|
]
|
|
134
135
|
|
|
135
|
-
const menus = ref<AppMenu[]>(
|
|
136
|
+
const menus = ref<AppMenu[]>([])
|
|
136
137
|
const ready = ref(false)
|
|
138
|
+
let tabNavigationTarget: string | undefined
|
|
137
139
|
let adapter: AppNavigationAdapter = {
|
|
138
140
|
list: () => defBaseMenuService.ListBaseMenu(),
|
|
139
141
|
}
|
|
@@ -147,7 +149,7 @@ export function setAppNavigationAdapter(nextAdapter: AppNavigationAdapter): void
|
|
|
147
149
|
export async function initializeAppNavigation(): Promise<void> {
|
|
148
150
|
const cacheKey = resolveCacheKey()
|
|
149
151
|
const cached = readCachedMenus(cacheKey)
|
|
150
|
-
menus.value = cached ??
|
|
152
|
+
menus.value = cached ?? createDefaultMenus()
|
|
151
153
|
try {
|
|
152
154
|
const nextMenus = normalizeMenuResponse(await adapter.list())
|
|
153
155
|
validateMenus(nextMenus)
|
|
@@ -207,13 +209,31 @@ export function navigateAppRoute(rawRoute: string, options: { replace?: boolean
|
|
|
207
209
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
208
210
|
.join('&')
|
|
209
211
|
const url = `/${resolved.physicalRoute}${query ? `?${query}` : ''}`
|
|
210
|
-
if (
|
|
212
|
+
if (resolved.menu.inTabBar) {
|
|
213
|
+
navigateTabRoute(url)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
if (options.replace) {
|
|
211
217
|
uni.reLaunch({ url })
|
|
212
218
|
return
|
|
213
219
|
}
|
|
214
220
|
uni.navigateTo({ url, fail: () => uni.reLaunch({ url }) })
|
|
215
221
|
}
|
|
216
222
|
|
|
223
|
+
/** 切换 tab 页面,避免当前页重复跳转和并发重建页面栈。 */
|
|
224
|
+
function navigateTabRoute(url: string): void {
|
|
225
|
+
const targetRoute = url.slice(1).split('?', 1)[0]
|
|
226
|
+
const pages = getCurrentPages()
|
|
227
|
+
const currentPage = pages[pages.length - 1]
|
|
228
|
+
if (currentPage?.route === targetRoute || tabNavigationTarget) return
|
|
229
|
+
|
|
230
|
+
tabNavigationTarget = targetRoute
|
|
231
|
+
const release = () => {
|
|
232
|
+
if (tabNavigationTarget === targetRoute) tabNavigationTarget = undefined
|
|
233
|
+
}
|
|
234
|
+
uni.reLaunch({ url, success: release, fail: release, complete: release })
|
|
235
|
+
}
|
|
236
|
+
|
|
217
237
|
/** 获取导航响应式状态。 */
|
|
218
238
|
export function useAppNavigation() {
|
|
219
239
|
const menuTree = computed<AppMenuNode[]>(() => buildMenuTree(menus.value, APP_MENU_ROOT_ID))
|
|
@@ -235,7 +255,8 @@ export function launchAppStatus(state: import('./module').BootstrapViewKey, deta
|
|
|
235
255
|
}
|
|
236
256
|
|
|
237
257
|
function resolveCacheKey(): string {
|
|
238
|
-
|
|
258
|
+
const identity = hasValidToken() ? AUTHENTICATED_CACHE_KEY : ANONYMOUS_CACHE_KEY
|
|
259
|
+
return `${identity}:${getCurrentLocale()}`
|
|
239
260
|
}
|
|
240
261
|
|
|
241
262
|
function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
|
|
@@ -252,15 +273,16 @@ function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
|
|
|
252
273
|
|
|
253
274
|
function normalizeMenuResponse(response: unknown): AppMenu[] {
|
|
254
275
|
if (Array.isArray(response)) return response.map(normalizeMenu)
|
|
255
|
-
if (!response || typeof response !== 'object')
|
|
276
|
+
if (!response || typeof response !== 'object')
|
|
277
|
+
throw new Error(t('core.navigation.error.response_object'))
|
|
256
278
|
const record = response as Record<string, unknown>
|
|
257
279
|
const list = record.items ?? record.list ?? record.data
|
|
258
|
-
if (!Array.isArray(list)) throw new Error('
|
|
280
|
+
if (!Array.isArray(list)) throw new Error(t('core.navigation.error.list_missing'))
|
|
259
281
|
return list.map(normalizeMenu)
|
|
260
282
|
}
|
|
261
283
|
|
|
262
284
|
function normalizeMenu(value: unknown): AppMenu {
|
|
263
|
-
if (!value || typeof value !== 'object') throw new Error('
|
|
285
|
+
if (!value || typeof value !== 'object') throw new Error(t('core.navigation.error.item_object'))
|
|
264
286
|
const item = value as Record<string, unknown>
|
|
265
287
|
const meta = (item.meta ?? {}) as Record<string, unknown>
|
|
266
288
|
const app = (meta.app ?? item.app ?? {}) as Record<string, unknown>
|
|
@@ -292,15 +314,19 @@ function validateMenus(nextMenus: AppMenu[]): void {
|
|
|
292
314
|
const paths = new Set<string>()
|
|
293
315
|
for (const menu of nextMenus) {
|
|
294
316
|
if (!Number.isSafeInteger(menu.id) || !menu.name || !menu.path.startsWith('app/')) {
|
|
295
|
-
throw new Error('
|
|
317
|
+
throw new Error(t('core.navigation.error.identity'))
|
|
318
|
+
}
|
|
319
|
+
if (!menu.name.startsWith('App')) {
|
|
320
|
+
throw new Error(t('core.navigation.error.name_prefix', { name: menu.name }))
|
|
296
321
|
}
|
|
297
|
-
if (!menu.name.startsWith('App')) throw new Error(`菜单名称必须使用 App 前缀:${menu.name}`)
|
|
298
322
|
if (!['PUBLIC', 'GUEST_ONLY', 'AUTHENTICATED'].includes(menu.access)) {
|
|
299
|
-
throw new Error(
|
|
323
|
+
throw new Error(t('core.navigation.error.access', { access: menu.access }))
|
|
324
|
+
}
|
|
325
|
+
if (!resolveStaticView(menu.viewKey)) {
|
|
326
|
+
throw new Error(t('core.navigation.error.view_key', { viewKey: menu.viewKey }))
|
|
300
327
|
}
|
|
301
|
-
if (!resolveStaticView(menu.viewKey)) throw new Error(`未注册 viewKey:${menu.viewKey}`)
|
|
302
328
|
if (ids.has(menu.id) || names.has(menu.name) || paths.has(menu.path)) {
|
|
303
|
-
throw new Error('
|
|
329
|
+
throw new Error(t('core.navigation.error.unique'))
|
|
304
330
|
}
|
|
305
331
|
ids.add(menu.id)
|
|
306
332
|
names.add(menu.name)
|
|
@@ -308,23 +334,28 @@ function validateMenus(nextMenus: AppMenu[]): void {
|
|
|
308
334
|
}
|
|
309
335
|
const menuMap = new Map(nextMenus.map((menu) => [menu.id, menu]))
|
|
310
336
|
for (const menu of nextMenus) {
|
|
311
|
-
if (menu.parentId === undefined)
|
|
337
|
+
if (menu.parentId === undefined) {
|
|
338
|
+
throw new Error(t('core.navigation.error.parent_missing', { name: menu.name }))
|
|
339
|
+
}
|
|
312
340
|
if (menu.parentId === APP_MENU_ROOT_ID) {
|
|
313
|
-
if (!menu.inTabBar) throw new Error(
|
|
341
|
+
if (!menu.inTabBar) throw new Error(t('core.navigation.error.root_tab', { name: menu.name }))
|
|
314
342
|
continue
|
|
315
343
|
}
|
|
316
|
-
if (menu.inTabBar) throw new Error(
|
|
344
|
+
if (menu.inTabBar) throw new Error(t('core.navigation.error.child_tab', { name: menu.name }))
|
|
317
345
|
const visited = new Set<number>([menu.id])
|
|
318
346
|
let parentId = menu.parentId
|
|
319
347
|
while (parentId !== APP_MENU_ROOT_ID) {
|
|
320
|
-
if (visited.has(parentId))
|
|
348
|
+
if (visited.has(parentId))
|
|
349
|
+
throw new Error(t('core.navigation.error.cycle', { name: menu.name }))
|
|
321
350
|
visited.add(parentId)
|
|
322
351
|
const parent = menuMap.get(parentId)
|
|
323
|
-
if (!parent) throw new Error(
|
|
324
|
-
if (parent.parentId === undefined)
|
|
352
|
+
if (!parent) throw new Error(t('core.navigation.error.parent_not_found', { name: menu.name }))
|
|
353
|
+
if (parent.parentId === undefined) {
|
|
354
|
+
throw new Error(t('core.navigation.error.parent_invalid', { name: menu.name }))
|
|
355
|
+
}
|
|
325
356
|
parentId = parent.parentId
|
|
326
357
|
}
|
|
327
358
|
}
|
|
328
359
|
const tabCount = nextMenus.filter((menu) => menu.parentId === APP_MENU_ROOT_ID).length
|
|
329
|
-
if (tabCount === 1 || tabCount > 5) throw new Error('
|
|
360
|
+
if (tabCount === 1 || tabCount > 5) throw new Error(t('core.navigation.error.tab_count'))
|
|
330
361
|
}
|
package/src/pages.ts
CHANGED
|
@@ -6,11 +6,11 @@ export const corePages: Record<string, KratosAppPageConfig> = {
|
|
|
6
6
|
style: {
|
|
7
7
|
navigationStyle: 'custom',
|
|
8
8
|
navigationBarTextStyle: 'white',
|
|
9
|
-
navigationBarTitleText: '
|
|
9
|
+
navigationBarTitleText: '',
|
|
10
10
|
},
|
|
11
11
|
},
|
|
12
|
-
'pages/login/login': { style: { navigationBarTitleText: '
|
|
13
|
-
'pages/login/protocal': { style: { navigationBarTitleText: '
|
|
12
|
+
'pages/login/login': { style: { navigationBarTitleText: '' } },
|
|
13
|
+
'pages/login/protocal': { style: { navigationBarTitleText: '' } },
|
|
14
14
|
'pages/webview/webview': {
|
|
15
15
|
style: {
|
|
16
16
|
navigationBarTitleText: '',
|
|
@@ -5,7 +5,18 @@
|
|
|
5
5
|
// source: base/v1/config.proto
|
|
6
6
|
|
|
7
7
|
/* eslint-disable */
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
/** 系统配置位置。 */
|
|
10
|
+
export enum BaseConfigSite {
|
|
11
|
+
/** BASE_CONFIG_SITE_UNSPECIFIED - 未指定系统配置位置。 */
|
|
12
|
+
BASE_CONFIG_SITE_UNSPECIFIED = 0,
|
|
13
|
+
/** BASE_CONFIG_SITE_SYSTEM - 系统内使用。 */
|
|
14
|
+
BASE_CONFIG_SITE_SYSTEM = 1,
|
|
15
|
+
/** BASE_CONFIG_SITE_ADMIN - 管理端。 */
|
|
16
|
+
BASE_CONFIG_SITE_ADMIN = 2,
|
|
17
|
+
/** BASE_CONFIG_SITE_APP - 移动端。 */
|
|
18
|
+
BASE_CONFIG_SITE_APP = 3,
|
|
19
|
+
}
|
|
9
20
|
|
|
10
21
|
/** 获取系统配置条件 */
|
|
11
22
|
export interface GetConfigRequest {
|
|
@@ -15,6 +26,8 @@ export interface GetConfigRequest {
|
|
|
15
26
|
|
|
16
27
|
/** 系统配置项 */
|
|
17
28
|
export interface ConfigItem {
|
|
29
|
+
/** 配置ID */
|
|
30
|
+
id: number;
|
|
18
31
|
/** 配置key */
|
|
19
32
|
key: string;
|
|
20
33
|
/** 配置value */
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.11.8
|
|
4
|
+
// protoc unknown
|
|
5
|
+
// source: base/v1/language.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
|
|
9
|
+
/** 查询语言选项请求参数。 */
|
|
10
|
+
export interface OptionLanguageRequest {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 语言选择项。 */
|
|
14
|
+
export interface LanguageItem {
|
|
15
|
+
/** 标准语言代码 */
|
|
16
|
+
language_code: string;
|
|
17
|
+
/** 本地语言名称 */
|
|
18
|
+
native_name: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 当前支持的语言选项查询结果。 */
|
|
22
|
+
export interface OptionLanguageResponse {
|
|
23
|
+
/** 当前支持的语言列表 */
|
|
24
|
+
languages: LanguageItem[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 语言公共服务。 */
|
|
28
|
+
export interface LanguageService {
|
|
29
|
+
/** 查询当前支持的语言选项。 */
|
|
30
|
+
OptionLanguage(request: OptionLanguageRequest): Promise<OptionLanguageResponse>;
|
|
31
|
+
}
|
package/src/rpc/base/v1/login.ts
CHANGED
|
@@ -7,7 +7,20 @@
|
|
|
7
7
|
/* eslint-disable */
|
|
8
8
|
import type { PasswordCrypto } from "../../common/v1/types";
|
|
9
9
|
import type { Empty } from "../../google/protobuf/empty";
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
/** 密码加密场景枚举。 */
|
|
12
|
+
export enum PasswordCryptoScene {
|
|
13
|
+
/** PASSWORD_CRYPTO_SCENE_UNSPECIFIED - 未指定密码加密场景。 */
|
|
14
|
+
PASSWORD_CRYPTO_SCENE_UNSPECIFIED = 0,
|
|
15
|
+
/** PASSWORD_CRYPTO_SCENE_LOGIN - 登录密码加密场景。 */
|
|
16
|
+
PASSWORD_CRYPTO_SCENE_LOGIN = 1,
|
|
17
|
+
/** PASSWORD_CRYPTO_SCENE_CREATE_BASE_USER - 新增后台用户密码加密场景。 */
|
|
18
|
+
PASSWORD_CRYPTO_SCENE_CREATE_BASE_USER = 2,
|
|
19
|
+
/** PASSWORD_CRYPTO_SCENE_RESET_BASE_USER_PASSWORD - 重置后台用户密码加密场景。 */
|
|
20
|
+
PASSWORD_CRYPTO_SCENE_RESET_BASE_USER_PASSWORD = 3,
|
|
21
|
+
/** PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD - 个人修改密码加密场景。 */
|
|
22
|
+
PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD = 4,
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
/** 验证码获取条件 */
|
|
13
26
|
export interface CaptchaRequest {
|
package/src/stores/index.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
getCurrentLocale,
|
|
5
|
+
getLanguageOptions,
|
|
6
|
+
getSupportedLocales,
|
|
7
|
+
localeState,
|
|
8
|
+
setCurrentLocale,
|
|
9
|
+
type SupportedLocale,
|
|
10
|
+
} from '../../locales'
|
|
11
|
+
|
|
12
|
+
/** 移动端语言状态。 */
|
|
13
|
+
export const useLocaleStore = defineStore('locale', () => {
|
|
14
|
+
const locale = computed(() => localeState.value)
|
|
15
|
+
|
|
16
|
+
/** 切换当前语言并持久化。 */
|
|
17
|
+
const setLocale = async (value: SupportedLocale) => {
|
|
18
|
+
await setCurrentLocale(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
locale,
|
|
23
|
+
currentLocale: getCurrentLocale,
|
|
24
|
+
languageOptions: computed(() => getLanguageOptions()),
|
|
25
|
+
supportedLocales: computed(() => getSupportedLocales()),
|
|
26
|
+
setLocale,
|
|
27
|
+
}
|
|
28
|
+
})
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { defConfigService } from '../../api/base/config'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { ref } from 'vue'
|
|
4
|
-
import { BaseConfigSite } from '../../rpc/base/v1/
|
|
4
|
+
import { BaseConfigSite } from '../../rpc/base/v1/config'
|
|
5
|
+
import { t } from '../../locales'
|
|
5
6
|
|
|
6
7
|
const REQUIRED_APP_CONFIGS = [
|
|
7
|
-
{ key: 'serviceProtocol',
|
|
8
|
-
{ key: 'privacyProtocol',
|
|
9
|
-
{ key: 'captchaType',
|
|
8
|
+
{ key: 'serviceProtocol', nameKey: 'core.protocol.service' },
|
|
9
|
+
{ key: 'privacyProtocol', nameKey: 'core.protocol.privacy' },
|
|
10
|
+
{ key: 'captchaType', nameKey: 'core.login.captcha_type_name' },
|
|
10
11
|
] as const
|
|
11
12
|
|
|
12
13
|
export const useSettingStore = defineStore('setting', () => {
|
|
@@ -26,7 +27,7 @@ export const useSettingStore = defineStore('setting', () => {
|
|
|
26
27
|
|
|
27
28
|
const request = (async () => {
|
|
28
29
|
const res = await defConfigService.GetConfig({
|
|
29
|
-
site: BaseConfigSite.
|
|
30
|
+
site: BaseConfigSite.BASE_CONFIG_SITE_APP,
|
|
30
31
|
})
|
|
31
32
|
const nextData = new Map<string, string>()
|
|
32
33
|
res.configs.forEach((item) => {
|
|
@@ -35,7 +36,11 @@ export const useSettingStore = defineStore('setting', () => {
|
|
|
35
36
|
|
|
36
37
|
const missingConfigs = REQUIRED_APP_CONFIGS.filter(({ key }) => !nextData.get(key))
|
|
37
38
|
if (missingConfigs.length) {
|
|
38
|
-
throw new Error(
|
|
39
|
+
throw new Error(
|
|
40
|
+
t('core.config.missing', {
|
|
41
|
+
names: missingConfigs.map(({ nameKey }) => t(nameKey)).join(', '),
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
data.value = nextData
|
package/src/utils/file.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { FileInfo, MultiUploadFileResponse } from '../rpc/base/v1/file'
|
|
2
2
|
import { formatSrc } from './index'
|
|
3
|
+
import { getLocaleRequestHeaders, t } from '../locales'
|
|
4
|
+
import { getRequestAccessToken } from './http'
|
|
3
5
|
|
|
4
6
|
// 文件上传-兼容小程序端、H5端、App端
|
|
5
7
|
export const uploadFile = async (fileType: string, filePath: string): Promise<FileInfo> => {
|
|
8
|
+
const token = await getRequestAccessToken()
|
|
6
9
|
const res = await uni.uploadFile({
|
|
7
10
|
url: '/v1/base/file',
|
|
8
11
|
name: 'file',
|
|
@@ -10,11 +13,16 @@ export const uploadFile = async (fileType: string, filePath: string): Promise<Fi
|
|
|
10
13
|
formData: {
|
|
11
14
|
fileType: fileType,
|
|
12
15
|
},
|
|
16
|
+
header: {
|
|
17
|
+
...getLocaleRequestHeaders(),
|
|
18
|
+
'source-client': 'miniapp',
|
|
19
|
+
...(token ? { Authorization: token } : {}),
|
|
20
|
+
},
|
|
13
21
|
})
|
|
14
22
|
if (res.statusCode === 200) {
|
|
15
23
|
return JSON.parse(res.data) as FileInfo
|
|
16
24
|
} else {
|
|
17
|
-
throw new Error('
|
|
25
|
+
throw new Error(t('core.file.upload_failed'))
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
28
|
|
|
@@ -23,35 +31,15 @@ export const uploadFileList = async (
|
|
|
23
31
|
fileType: string,
|
|
24
32
|
filePaths: string[],
|
|
25
33
|
): Promise<FileInfo[]> => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
await Promise.all(
|
|
29
|
-
filePaths.map(async (filePath) => {
|
|
30
|
-
try {
|
|
31
|
-
const res = await uni.uploadFile({
|
|
32
|
-
url: '/v1/base/file',
|
|
33
|
-
name: 'file',
|
|
34
|
-
filePath: filePath,
|
|
35
|
-
formData: {
|
|
36
|
-
fileType: fileType,
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
if (res.statusCode === 200) {
|
|
40
|
-
const data = JSON.parse(res.data) as FileInfo
|
|
41
|
-
fileInfoArr.push(data)
|
|
42
|
-
} else {
|
|
43
|
-
console.error('上传失败,状态码:', res.statusCode)
|
|
44
|
-
}
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('上传异常:', error)
|
|
47
|
-
}
|
|
48
|
-
}),
|
|
34
|
+
const results = await Promise.allSettled(
|
|
35
|
+
filePaths.map((filePath) => uploadFile(fileType, filePath)),
|
|
49
36
|
)
|
|
50
|
-
return
|
|
37
|
+
return results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []))
|
|
51
38
|
}
|
|
52
39
|
|
|
53
40
|
// 多文件上传-兼容小程序端、H5端、App端
|
|
54
41
|
export const multiUploadFile = async (fileType: string, files: any): Promise<FileInfo[]> => {
|
|
42
|
+
const token = await getRequestAccessToken()
|
|
55
43
|
const res = await uni.uploadFile({
|
|
56
44
|
url: '/v1/base/file/multi',
|
|
57
45
|
name: 'file',
|
|
@@ -60,12 +48,17 @@ export const multiUploadFile = async (fileType: string, files: any): Promise<Fil
|
|
|
60
48
|
formData: {
|
|
61
49
|
fileType: fileType,
|
|
62
50
|
},
|
|
51
|
+
header: {
|
|
52
|
+
...getLocaleRequestHeaders(),
|
|
53
|
+
'source-client': 'miniapp',
|
|
54
|
+
...(token ? { Authorization: token } : {}),
|
|
55
|
+
},
|
|
63
56
|
})
|
|
64
57
|
if (res.statusCode === 200) {
|
|
65
58
|
const data = JSON.parse(res.data) as MultiUploadFileResponse
|
|
66
59
|
return data.files
|
|
67
60
|
} else {
|
|
68
|
-
await uni.showToast({ icon: 'error', title: '
|
|
61
|
+
await uni.showToast({ icon: 'error', title: t('core.file.upload_failed') })
|
|
69
62
|
return []
|
|
70
63
|
}
|
|
71
64
|
}
|
package/src/utils/http.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
shouldRefreshToken,
|
|
17
17
|
} from './auth'
|
|
18
18
|
import { saveCurrentRoute } from './navigation'
|
|
19
|
+
import { getLocaleRequestHeaders, t } from '../locales'
|
|
19
20
|
|
|
20
21
|
const apiBasePath = import.meta.env.VITE_APP_BASE_API || '/api'
|
|
21
22
|
const apiTargetUrl = import.meta.env.VITE_APP_API_URL || ''
|
|
@@ -35,6 +36,7 @@ const SESSION_URL = '/v1/base/session'
|
|
|
35
36
|
const REFRESH_TOKEN_URL = '/v1/base/token'
|
|
36
37
|
const CAPTCHA_URL = '/v1/base/captcha'
|
|
37
38
|
const CONFIG_URL = '/v1/base/config'
|
|
39
|
+
const LANGUAGE_URL = '/v1/base/language'
|
|
38
40
|
const PASSWORD_PUBLIC_KEY_URL = '/v1/base/password-public-key'
|
|
39
41
|
const LEGACY_AUTH_URL = '/auth'
|
|
40
42
|
const LEGACY_REFRESH_TOKEN_URL = `${LEGACY_AUTH_URL}/token`
|
|
@@ -45,6 +47,7 @@ const NO_AUTH_URL_SET = new Set([
|
|
|
45
47
|
REFRESH_TOKEN_URL,
|
|
46
48
|
CAPTCHA_URL,
|
|
47
49
|
CONFIG_URL,
|
|
50
|
+
LANGUAGE_URL,
|
|
48
51
|
PASSWORD_PUBLIC_KEY_URL,
|
|
49
52
|
LEGACY_AUTH_URL,
|
|
50
53
|
LEGACY_CAPTCHA_URL,
|
|
@@ -54,6 +57,7 @@ const AUTH_EXPIRED_EXCLUDED_URL_SET = new Set([
|
|
|
54
57
|
SESSION_URL,
|
|
55
58
|
CAPTCHA_URL,
|
|
56
59
|
CONFIG_URL,
|
|
60
|
+
LANGUAGE_URL,
|
|
57
61
|
PASSWORD_PUBLIC_KEY_URL,
|
|
58
62
|
LEGACY_AUTH_URL,
|
|
59
63
|
LEGACY_CAPTCHA_URL,
|
|
@@ -87,6 +91,7 @@ const httpInterceptor = {
|
|
|
87
91
|
options.header = {
|
|
88
92
|
...options.header,
|
|
89
93
|
'source-client': 'miniapp',
|
|
94
|
+
...getLocaleRequestHeaders(),
|
|
90
95
|
}
|
|
91
96
|
// 4. 添加 token 请求头标识
|
|
92
97
|
const accessToken = getToken()
|
|
@@ -219,7 +224,7 @@ export const http = <T>(options: HttpRequestOptions) => {
|
|
|
219
224
|
// 其他错误 -> 根据后端错误信息轻提示
|
|
220
225
|
void uni.showToast({
|
|
221
226
|
icon: 'none',
|
|
222
|
-
title: responseData.message || '
|
|
227
|
+
title: responseData.message || t('common.message.request_error'),
|
|
223
228
|
})
|
|
224
229
|
reject(res)
|
|
225
230
|
}
|
|
@@ -228,7 +233,7 @@ export const http = <T>(options: HttpRequestOptions) => {
|
|
|
228
233
|
fail(err) {
|
|
229
234
|
void uni.showToast({
|
|
230
235
|
icon: 'none',
|
|
231
|
-
title: '
|
|
236
|
+
title: t('common.message.network_error'),
|
|
232
237
|
})
|
|
233
238
|
reject(err)
|
|
234
239
|
},
|
|
@@ -326,6 +331,7 @@ async function refreshAccessToken() {
|
|
|
326
331
|
data: { refresh_token: refreshToken },
|
|
327
332
|
header: {
|
|
328
333
|
'source-client': 'miniapp',
|
|
334
|
+
...getLocaleRequestHeaders(),
|
|
329
335
|
},
|
|
330
336
|
success: resolve,
|
|
331
337
|
fail: reject,
|
|
@@ -377,10 +383,10 @@ async function promptRelogin() {
|
|
|
377
383
|
isPromptingRelogin = true
|
|
378
384
|
try {
|
|
379
385
|
const modalRes = await uni.showModal({
|
|
380
|
-
title: '
|
|
381
|
-
content: '
|
|
386
|
+
title: t('common.title.notice'),
|
|
387
|
+
content: t('core.auth.session_expired'),
|
|
382
388
|
showCancel: false,
|
|
383
|
-
confirmText: '
|
|
389
|
+
confirmText: t('core.auth.login_again'),
|
|
384
390
|
})
|
|
385
391
|
|
|
386
392
|
if (!modalRes.confirm) {
|
|
@@ -405,7 +411,7 @@ function handleAuthExpiredByMode(authMode: AuthMode, url: string, responseData:
|
|
|
405
411
|
if (authMode !== 'optional') {
|
|
406
412
|
void uni.showToast({
|
|
407
413
|
icon: 'none',
|
|
408
|
-
title: responseData.message || '
|
|
414
|
+
title: responseData.message || t('common.message.request_error'),
|
|
409
415
|
})
|
|
410
416
|
}
|
|
411
417
|
}
|