@liujitcn/kratos-uni-app-core 0.0.20 → 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/module.mjs +796 -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 +110 -0
- package/src/locales/es-ES.json +110 -0
- package/src/locales/fr-FR.json +110 -0
- package/src/locales/index.ts +235 -0
- package/src/locales/ja-JP.json +110 -0
- package/src/locales/ko-KR.json +110 -0
- package/src/locales/zh-CN.json +110 -0
- package/src/locales/zh-TW.json +110 -0
- package/src/module.ts +21 -0
- package/src/navigation.ts +38 -26
- 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/index.ts +1 -0
- package/src/stores/modules/locale.ts +28 -0
- package/src/stores/modules/setting.ts +9 -4
- package/src/utils/file.ts +19 -26
- package/src/utils/http.ts +12 -6
- package/src/utils/passwordCrypto.ts +16 -15
- package/src/views/pages/index/index.vue +16 -14
- package/src/views/pages/login/login.vue +105 -51
- package/src/views/pages/login/protocal.vue +5 -3
- package/src/views/pages/status/index.vue +7 -10
- package/src/views/pages/webview/webview.vue +6 -4
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common.action.cancel": "取消",
|
|
3
|
+
"common.action.confirm": "確定",
|
|
4
|
+
"common.action.delete": "刪除",
|
|
5
|
+
"common.action.login": "登入",
|
|
6
|
+
"common.action.logout": "退出登入",
|
|
7
|
+
"common.action.new": "新建",
|
|
8
|
+
"common.action.openInNewWindow": "新視窗開啟",
|
|
9
|
+
"common.action.retry": "重試",
|
|
10
|
+
"common.action.save": "儲存",
|
|
11
|
+
"common.action.search": "搜尋",
|
|
12
|
+
"common.field.language": "語言",
|
|
13
|
+
"common.language.en-US": "English",
|
|
14
|
+
"common.language.ja-JP": "日本語",
|
|
15
|
+
"common.language.zh-CN": "簡體中文",
|
|
16
|
+
"common.language.zh-TW": "繁體中文",
|
|
17
|
+
"common.language.ko-KR": "韓語",
|
|
18
|
+
"common.language.fr-FR": "法語",
|
|
19
|
+
"common.language.es-ES": "西班牙語",
|
|
20
|
+
"common.message.loading": "正在載入",
|
|
21
|
+
"common.message.networkError": "網路錯誤,換個網路試試",
|
|
22
|
+
"common.message.requestError": "請求錯誤",
|
|
23
|
+
"common.message.unknown": "暫時無法處理,請稍後重試",
|
|
24
|
+
"common.title.notice": "提示",
|
|
25
|
+
"core.auth.loginAgain": "重新登入",
|
|
26
|
+
"core.auth.sessionExpired": "當前頁面已失效,請重新登入",
|
|
27
|
+
"core.config.missing": "移動端配置缺失:{names}",
|
|
28
|
+
"core.crypto.base64DecodeUnsupported": "當前環境不支援 Base64 解碼",
|
|
29
|
+
"core.crypto.base64EncodeUnsupported": "當前環境不支援 Base64 編碼",
|
|
30
|
+
"core.crypto.encryptFailed": "密碼加密失敗",
|
|
31
|
+
"core.crypto.passwordRequired": "密碼不能為空",
|
|
32
|
+
"core.crypto.passwordTooLong": "密碼長度超出限制",
|
|
33
|
+
"core.crypto.publicKeyInvalid": "公鑰格式無效",
|
|
34
|
+
"core.crypto.randomFailed": "隨機數生成失敗",
|
|
35
|
+
"core.crypto.unsupported": "當前環境不支援密碼加密",
|
|
36
|
+
"core.file.uploadFailed": "上傳失敗",
|
|
37
|
+
"core.home.accountCapability": "賬戶能力",
|
|
38
|
+
"core.home.accountCapabilityDescription": "個人資料、應用設定與智慧助手入口已保留",
|
|
39
|
+
"core.home.crossPlatform": "跨端頁面",
|
|
40
|
+
"core.home.crossPlatformDescription": "同一套頁面程式碼適配 H5 和微信小程式",
|
|
41
|
+
"core.home.demoScope": "演示範圍",
|
|
42
|
+
"core.home.framework": "應用框架",
|
|
43
|
+
"core.home.language": "開發語言",
|
|
44
|
+
"core.home.mainTitle": "應用框架示例",
|
|
45
|
+
"core.home.stateManagement": "狀態管理",
|
|
46
|
+
"core.home.staticHome": "靜態首頁",
|
|
47
|
+
"core.home.staticHomeDescription": "當前首頁用於展示框架資訊,不依賴業務介面",
|
|
48
|
+
"core.home.styleSolution": "樣式方案",
|
|
49
|
+
"core.home.subTitle": "保留通用導航與個人中心體驗",
|
|
50
|
+
"core.home.techStack": "當前技術棧",
|
|
51
|
+
"core.login.agreePrefix": "我已閱讀並同意",
|
|
52
|
+
"core.login.agreeSeparator": "和",
|
|
53
|
+
"core.login.behaviorClick": "請按順序點選文字",
|
|
54
|
+
"core.login.behaviorLoading": "載入中...",
|
|
55
|
+
"core.login.behaviorPuzzle": "請拖動滑塊完成拼圖",
|
|
56
|
+
"core.login.behaviorRotate": "拖動滑塊,將內圈圖片轉正",
|
|
57
|
+
"core.login.bindWechat": "登入並繫結微信",
|
|
58
|
+
"core.login.captcha": "請輸入驗證碼",
|
|
59
|
+
"core.login.captchaLoadFailed": "驗證碼載入失敗",
|
|
60
|
+
"core.login.captchaTypeMissing": "登入驗證碼型別未配置",
|
|
61
|
+
"core.login.captchaTypeName": "登入驗證碼型別",
|
|
62
|
+
"core.login.captchaTypeResponseMissing": "驗證碼介面未返回型別",
|
|
63
|
+
"core.login.captchaWriteFailed": "驗證碼圖片寫入失敗",
|
|
64
|
+
"core.login.defaultSubTitle": "歡迎使用本應用",
|
|
65
|
+
"core.login.loginFailed": "登入失敗,請重試",
|
|
66
|
+
"core.login.loginSuccess": "登入成功",
|
|
67
|
+
"core.login.password": "請輸入密碼",
|
|
68
|
+
"core.login.privacy": "《隱私協議》",
|
|
69
|
+
"core.login.protocolMissing": "服務條款和隱私協議未配置",
|
|
70
|
+
"core.login.protocolPrompt": "請先閱讀並勾選協議內容,點選確定後將自動勾選並繼續登入",
|
|
71
|
+
"core.login.service": "《服務條款》",
|
|
72
|
+
"core.login.tenant": "請輸入租戶編號",
|
|
73
|
+
"core.login.userName": "請輸入使用者名稱或手機號",
|
|
74
|
+
"core.login.userNameMobile": "請輸入使用者名稱/手機號碼",
|
|
75
|
+
"core.login.userNamePassword": "請輸入使用者名稱和密碼",
|
|
76
|
+
"core.login.wechat": "微信一鍵登入",
|
|
77
|
+
"core.login.wechatBindTip": "該微信尚未繫結,請登入已有賬號完成繫結",
|
|
78
|
+
"core.login.wechatFailed": "微信登入失敗,請稍後重試",
|
|
79
|
+
"core.navigation.home": "首頁",
|
|
80
|
+
"core.navigation.error.access": "選單訪問模式無效:{access}",
|
|
81
|
+
"core.navigation.error.childTab": "只有移動端二級選單可以作為 tab:{name}",
|
|
82
|
+
"core.navigation.error.cycle": "移動端選單存在迴圈父級:{name}",
|
|
83
|
+
"core.navigation.error.identity": "選單 id、name 或 app/ 路徑無效",
|
|
84
|
+
"core.navigation.error.itemObject": "選單項不是物件",
|
|
85
|
+
"core.navigation.error.listMissing": "選單響應缺少扁平列表",
|
|
86
|
+
"core.navigation.error.namePrefix": "選單名稱必須使用 App 字首:{name}",
|
|
87
|
+
"core.navigation.error.parentInvalid": "移動端選單父級編號無效:{name}",
|
|
88
|
+
"core.navigation.error.parentMissing": "選單缺少父級編號:{name}",
|
|
89
|
+
"core.navigation.error.parentNotFound": "移動端選單父級不存在:{name}",
|
|
90
|
+
"core.navigation.error.responseObject": "選單響應不是物件",
|
|
91
|
+
"core.navigation.error.rootTab": "移動端二級選單必須作為 tab:{name}",
|
|
92
|
+
"core.navigation.error.tabCount": "tabBar 只能配置 0 或 2-5 項",
|
|
93
|
+
"core.navigation.error.unique": "選單 id、name、path 必須唯一",
|
|
94
|
+
"core.navigation.error.viewKey": "未註冊 viewKey:{viewKey}",
|
|
95
|
+
"core.navigation.my": "我的",
|
|
96
|
+
"core.navigation.protocol": "協議詳情",
|
|
97
|
+
"core.protocol.loadFailed": "移動端配置載入失敗",
|
|
98
|
+
"core.protocol.notConfigured": "{title}未配置,暫時無法檢視",
|
|
99
|
+
"core.protocol.privacy": "隱私協議",
|
|
100
|
+
"core.protocol.service": "服務條款",
|
|
101
|
+
"core.status.CONFIG_ERROR": "導航配置無效",
|
|
102
|
+
"core.status.FORBIDDEN": "暫無訪問許可權",
|
|
103
|
+
"core.status.NOT_FOUND": "頁面不存在",
|
|
104
|
+
"core.status.OFFLINE": "網路不可用",
|
|
105
|
+
"core.status.PAGE_UNAVAILABLE": "頁面暫不可用",
|
|
106
|
+
"core.status.loading": "正在載入",
|
|
107
|
+
"core.webview.embedBlocked": "當前 H5 頁面可能被目標站點限制嵌入",
|
|
108
|
+
"core.webview.invalidUrl": "缺少有效連結地址",
|
|
109
|
+
"core.webview.openFailed": "連結無法開啟"
|
|
110
|
+
}
|
package/src/module.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { corePages } from './pages'
|
|
2
|
+
import type { LocaleMessages, SupportedLocale } from './locales'
|
|
3
|
+
|
|
4
|
+
// Core 语言包随 coreModule 注册,供 uni-app 公共页面、请求工具和基础组件使用。
|
|
5
|
+
import coreEnUS from './locales/en-US.json'
|
|
6
|
+
import coreJaJP from './locales/ja-JP.json'
|
|
7
|
+
import coreZhCN from './locales/zh-CN.json'
|
|
8
|
+
import coreZhTW from './locales/zh-TW.json'
|
|
9
|
+
import coreKoKR from './locales/ko-KR.json'
|
|
10
|
+
import coreFrFR from './locales/fr-FR.json'
|
|
11
|
+
import coreEsES from './locales/es-ES.json'
|
|
2
12
|
|
|
3
13
|
/** bootstrap 可见状态键。 */
|
|
4
14
|
export type BootstrapViewKey =
|
|
@@ -21,6 +31,8 @@ export interface KratosAppModule {
|
|
|
21
31
|
pages: Record<string, KratosAppPageConfig>
|
|
22
32
|
views: Record<string, string>
|
|
23
33
|
icons?: Record<string, string>
|
|
34
|
+
/** 模块按语言区域贡献的扁平语言包。 */
|
|
35
|
+
messages?: Partial<Record<SupportedLocale, LocaleMessages>>
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
/** 声明 uni-app 模块。 */
|
|
@@ -82,4 +94,13 @@ export const coreModule = defineKratosAppModule({
|
|
|
82
94
|
USER_DEFAULT: 'static/tabs/user_default.png',
|
|
83
95
|
USER_SELECTED: 'static/tabs/user_selected.png',
|
|
84
96
|
},
|
|
97
|
+
messages: {
|
|
98
|
+
'zh-CN': coreZhCN,
|
|
99
|
+
'zh-TW': coreZhTW,
|
|
100
|
+
'ko-KR': coreKoKR,
|
|
101
|
+
'fr-FR': coreFrFR,
|
|
102
|
+
'es-ES': coreEsES,
|
|
103
|
+
'en-US': coreEnUS,
|
|
104
|
+
'ja-JP': coreJaJP,
|
|
105
|
+
},
|
|
85
106
|
})
|
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.chatTitle'),
|
|
120
121
|
access: 'AUTHENTICATED',
|
|
121
122
|
inTabBar: false,
|
|
122
123
|
},
|
|
@@ -132,7 +133,7 @@ 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)
|
|
137
138
|
let adapter: AppNavigationAdapter = {
|
|
138
139
|
list: () => defBaseMenuService.ListBaseMenu(),
|
|
@@ -147,7 +148,7 @@ export function setAppNavigationAdapter(nextAdapter: AppNavigationAdapter): void
|
|
|
147
148
|
export async function initializeAppNavigation(): Promise<void> {
|
|
148
149
|
const cacheKey = resolveCacheKey()
|
|
149
150
|
const cached = readCachedMenus(cacheKey)
|
|
150
|
-
menus.value = cached ??
|
|
151
|
+
menus.value = cached ?? createDefaultMenus()
|
|
151
152
|
try {
|
|
152
153
|
const nextMenus = normalizeMenuResponse(await adapter.list())
|
|
153
154
|
validateMenus(nextMenus)
|
|
@@ -235,7 +236,8 @@ export function launchAppStatus(state: import('./module').BootstrapViewKey, deta
|
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
function resolveCacheKey(): string {
|
|
238
|
-
|
|
239
|
+
const identity = hasValidToken() ? AUTHENTICATED_CACHE_KEY : ANONYMOUS_CACHE_KEY
|
|
240
|
+
return `${identity}:${getCurrentLocale()}`
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
|
|
@@ -252,15 +254,16 @@ function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
|
|
|
252
254
|
|
|
253
255
|
function normalizeMenuResponse(response: unknown): AppMenu[] {
|
|
254
256
|
if (Array.isArray(response)) return response.map(normalizeMenu)
|
|
255
|
-
if (!response || typeof response !== 'object')
|
|
257
|
+
if (!response || typeof response !== 'object')
|
|
258
|
+
throw new Error(t('core.navigation.error.responseObject'))
|
|
256
259
|
const record = response as Record<string, unknown>
|
|
257
260
|
const list = record.items ?? record.list ?? record.data
|
|
258
|
-
if (!Array.isArray(list)) throw new Error('
|
|
261
|
+
if (!Array.isArray(list)) throw new Error(t('core.navigation.error.listMissing'))
|
|
259
262
|
return list.map(normalizeMenu)
|
|
260
263
|
}
|
|
261
264
|
|
|
262
265
|
function normalizeMenu(value: unknown): AppMenu {
|
|
263
|
-
if (!value || typeof value !== 'object') throw new Error('
|
|
266
|
+
if (!value || typeof value !== 'object') throw new Error(t('core.navigation.error.itemObject'))
|
|
264
267
|
const item = value as Record<string, unknown>
|
|
265
268
|
const meta = (item.meta ?? {}) as Record<string, unknown>
|
|
266
269
|
const app = (meta.app ?? item.app ?? {}) as Record<string, unknown>
|
|
@@ -292,15 +295,19 @@ function validateMenus(nextMenus: AppMenu[]): void {
|
|
|
292
295
|
const paths = new Set<string>()
|
|
293
296
|
for (const menu of nextMenus) {
|
|
294
297
|
if (!Number.isSafeInteger(menu.id) || !menu.name || !menu.path.startsWith('app/')) {
|
|
295
|
-
throw new Error('
|
|
298
|
+
throw new Error(t('core.navigation.error.identity'))
|
|
299
|
+
}
|
|
300
|
+
if (!menu.name.startsWith('App')) {
|
|
301
|
+
throw new Error(t('core.navigation.error.namePrefix', { name: menu.name }))
|
|
296
302
|
}
|
|
297
|
-
if (!menu.name.startsWith('App')) throw new Error(`菜单名称必须使用 App 前缀:${menu.name}`)
|
|
298
303
|
if (!['PUBLIC', 'GUEST_ONLY', 'AUTHENTICATED'].includes(menu.access)) {
|
|
299
|
-
throw new Error(
|
|
304
|
+
throw new Error(t('core.navigation.error.access', { access: menu.access }))
|
|
305
|
+
}
|
|
306
|
+
if (!resolveStaticView(menu.viewKey)) {
|
|
307
|
+
throw new Error(t('core.navigation.error.viewKey', { viewKey: menu.viewKey }))
|
|
300
308
|
}
|
|
301
|
-
if (!resolveStaticView(menu.viewKey)) throw new Error(`未注册 viewKey:${menu.viewKey}`)
|
|
302
309
|
if (ids.has(menu.id) || names.has(menu.name) || paths.has(menu.path)) {
|
|
303
|
-
throw new Error('
|
|
310
|
+
throw new Error(t('core.navigation.error.unique'))
|
|
304
311
|
}
|
|
305
312
|
ids.add(menu.id)
|
|
306
313
|
names.add(menu.name)
|
|
@@ -308,23 +315,28 @@ function validateMenus(nextMenus: AppMenu[]): void {
|
|
|
308
315
|
}
|
|
309
316
|
const menuMap = new Map(nextMenus.map((menu) => [menu.id, menu]))
|
|
310
317
|
for (const menu of nextMenus) {
|
|
311
|
-
if (menu.parentId === undefined)
|
|
318
|
+
if (menu.parentId === undefined) {
|
|
319
|
+
throw new Error(t('core.navigation.error.parentMissing', { name: menu.name }))
|
|
320
|
+
}
|
|
312
321
|
if (menu.parentId === APP_MENU_ROOT_ID) {
|
|
313
|
-
if (!menu.inTabBar) throw new Error(
|
|
322
|
+
if (!menu.inTabBar) throw new Error(t('core.navigation.error.rootTab', { name: menu.name }))
|
|
314
323
|
continue
|
|
315
324
|
}
|
|
316
|
-
if (menu.inTabBar) throw new Error(
|
|
325
|
+
if (menu.inTabBar) throw new Error(t('core.navigation.error.childTab', { name: menu.name }))
|
|
317
326
|
const visited = new Set<number>([menu.id])
|
|
318
327
|
let parentId = menu.parentId
|
|
319
328
|
while (parentId !== APP_MENU_ROOT_ID) {
|
|
320
|
-
if (visited.has(parentId))
|
|
329
|
+
if (visited.has(parentId))
|
|
330
|
+
throw new Error(t('core.navigation.error.cycle', { name: menu.name }))
|
|
321
331
|
visited.add(parentId)
|
|
322
332
|
const parent = menuMap.get(parentId)
|
|
323
|
-
if (!parent) throw new Error(
|
|
324
|
-
if (parent.parentId === undefined)
|
|
333
|
+
if (!parent) throw new Error(t('core.navigation.error.parentNotFound', { name: menu.name }))
|
|
334
|
+
if (parent.parentId === undefined) {
|
|
335
|
+
throw new Error(t('core.navigation.error.parentInvalid', { name: menu.name }))
|
|
336
|
+
}
|
|
325
337
|
parentId = parent.parentId
|
|
326
338
|
}
|
|
327
339
|
}
|
|
328
340
|
const tabCount = nextMenus.filter((menu) => menu.parentId === APP_MENU_ROOT_ID).length
|
|
329
|
-
if (tabCount === 1 || tabCount > 5) throw new Error('
|
|
341
|
+
if (tabCount === 1 || tabCount > 5) throw new Error(t('core.navigation.error.tabCount'))
|
|
330
342
|
}
|
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: '',
|
|
@@ -0,0 +1,37 @@
|
|
|
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 GetLanguageRequest {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 支持的语言。 */
|
|
14
|
+
export interface LanguageItem {
|
|
15
|
+
/** 标准语言代码 */
|
|
16
|
+
language_code: string;
|
|
17
|
+
/** 语言名称 */
|
|
18
|
+
language_name: string;
|
|
19
|
+
/** 本地语言名称 */
|
|
20
|
+
native_name: string;
|
|
21
|
+
/** 排序,值越小越靠前 */
|
|
22
|
+
sort: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 当前支持的语言查询结果。 */
|
|
26
|
+
export interface GetLanguageResponse {
|
|
27
|
+
/** 当前支持的语言列表 */
|
|
28
|
+
languages: LanguageItem[];
|
|
29
|
+
/** 主语言代码 */
|
|
30
|
+
primary_language_code: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 语言公共服务。 */
|
|
34
|
+
export interface LanguageService {
|
|
35
|
+
/** 查询当前支持的语言和主语言。 */
|
|
36
|
+
GetLanguage(request: GetLanguageRequest): Promise<GetLanguageResponse>;
|
|
37
|
+
}
|
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
|
+
})
|
|
@@ -2,11 +2,12 @@ import { defConfigService } from '../../api/base/config'
|
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { ref } from 'vue'
|
|
4
4
|
import { BaseConfigSite } from '../../rpc/base/v1/enum'
|
|
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.captchaTypeName' },
|
|
10
11
|
] as const
|
|
11
12
|
|
|
12
13
|
export const useSettingStore = defineStore('setting', () => {
|
|
@@ -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.uploadFailed'))
|
|
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.uploadFailed') })
|
|
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.requestError'),
|
|
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.networkError'),
|
|
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.sessionExpired'),
|
|
382
388
|
showCancel: false,
|
|
383
|
-
confirmText: '
|
|
389
|
+
confirmText: t('core.auth.loginAgain'),
|
|
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.requestError'),
|
|
409
415
|
})
|
|
410
416
|
}
|
|
411
417
|
}
|