@liujitcn/kratos-uni-app-core 0.0.31 → 0.0.33
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 +2 -2
- package/dist/module.mjs +0 -12
- package/dist/vite.mjs +4 -5
- package/package.json +1 -1
- package/src/api/base/{config.ts → v1/config.ts} +6 -2
- package/src/api/base/{file.ts → v1/file.ts} +6 -2
- package/src/api/base/{language.ts → v1/language.ts} +2 -2
- package/src/api/base/{login.ts → v1/login.ts} +3 -3
- package/src/api/base/{mfa.ts → v1/mfa.ts} +4 -4
- package/src/api/base/{oauth.ts → v1/oauth.ts} +3 -3
- package/src/api/system/{auth.ts → app/v1/auth.ts} +8 -15
- package/src/api/system/{base_area.ts → app/v1/base_area.ts} +2 -2
- package/src/api/system/app/v1/base_dict.ts +21 -0
- package/src/api/{base/menu.ts → system/app/v1/base_menu.ts} +2 -2
- package/src/bootstrap.ts +1 -1
- package/src/components/KratosSettingsPage.vue +13 -1
- package/src/components/KratosTabBar.vue +42 -21
- package/src/components/MfaManageDialog.vue +1 -1
- package/src/locales/en-US.json +0 -3
- package/src/locales/ja-JP.json +0 -3
- package/src/locales/zh-CN.json +0 -3
- package/src/locales/zh-TW.json +0 -3
- package/src/navigation.ts +69 -53
- package/src/rpc/base/v1/login.ts +4 -2
- package/src/rpc/system/app/v1/auth.ts +7 -0
- package/src/rpc/system/common/v1/common.ts +35 -0
- package/src/stores/modules/setting.ts +1 -1
- package/src/stores/modules/user.ts +4 -4
- package/src/utils/http.ts +2 -10
- package/src/utils/passwordCrypto.ts +41 -31
- package/src/views/pages/login/login.vue +7 -2
- package/src/vite.ts +4 -7
- package/src/api/system/base_dict.ts +0 -24
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
```text
|
|
8
8
|
packages/core
|
|
9
9
|
├── src
|
|
10
|
-
│ ├── api #
|
|
10
|
+
│ ├── api # 与 Proto 完整路径一致的接口 service
|
|
11
11
|
│ ├── components # 启动状态、自绘 tabBar、公共设置页和 MFA 组件
|
|
12
12
|
│ ├── rpc # Buf 生成的 TypeScript 接口类型
|
|
13
13
|
│ ├── static # 底座图片和 tab 图标
|
|
@@ -31,7 +31,7 @@ packages/core
|
|
|
31
31
|
## 功能
|
|
32
32
|
|
|
33
33
|
- 定义 `KratosAppModule`、`viewKey`、静态页面覆盖和模块图标协议。
|
|
34
|
-
- 从 `/v1/app/base/menu` 加载 `
|
|
34
|
+
- 从 `/v1/app/base/menu` 加载 `99000000` 下的扁平菜单,按 `parent_id` 构建树,以二级页面生成 tabBar,并原子更新动态路由和层级导航。
|
|
35
35
|
- 按匿名态、登录态缓存导航,并在远端不可用时回退到最近缓存或默认菜单。
|
|
36
36
|
- 统一认证、token、MFA、HTTP、上传、配置、Pinia 和基础页面能力。
|
|
37
37
|
- 提供可复用的 `KratosSettingsPage`,统一语言、MFA、微信设置和退出登录,并允许产品在最上方追加业务设置。
|
package/dist/module.mjs
CHANGED
|
@@ -121,13 +121,10 @@ var zh_CN_default = {
|
|
|
121
121
|
"core.navigation.error.child_tab": "\u53EA\u6709\u79FB\u52A8\u7AEF\u4E8C\u7EA7\u83DC\u5355\u53EF\u4EE5\u4F5C\u4E3A tab\uFF1A{name}",
|
|
122
122
|
"core.navigation.error.cycle": "\u79FB\u52A8\u7AEF\u83DC\u5355\u5B58\u5728\u5FAA\u73AF\u7236\u7EA7\uFF1A{name}",
|
|
123
123
|
"core.navigation.error.identity": "\u83DC\u5355 id\u3001name \u6216 app/ \u8DEF\u5F84\u65E0\u6548",
|
|
124
|
-
"core.navigation.error.item_object": "\u83DC\u5355\u9879\u4E0D\u662F\u5BF9\u8C61",
|
|
125
|
-
"core.navigation.error.list_missing": "\u83DC\u5355\u54CD\u5E94\u7F3A\u5C11\u6241\u5E73\u5217\u8868",
|
|
126
124
|
"core.navigation.error.name_prefix": "\u83DC\u5355\u540D\u79F0\u5FC5\u987B\u4F7F\u7528 App \u524D\u7F00\uFF1A{name}",
|
|
127
125
|
"core.navigation.error.parent_invalid": "\u79FB\u52A8\u7AEF\u83DC\u5355\u7236\u7EA7\u7F16\u53F7\u65E0\u6548\uFF1A{name}",
|
|
128
126
|
"core.navigation.error.parent_missing": "\u83DC\u5355\u7F3A\u5C11\u7236\u7EA7\u7F16\u53F7\uFF1A{name}",
|
|
129
127
|
"core.navigation.error.parent_not_found": "\u79FB\u52A8\u7AEF\u83DC\u5355\u7236\u7EA7\u4E0D\u5B58\u5728\uFF1A{name}",
|
|
130
|
-
"core.navigation.error.response_object": "\u83DC\u5355\u54CD\u5E94\u4E0D\u662F\u5BF9\u8C61",
|
|
131
128
|
"core.navigation.error.root_tab": "\u79FB\u52A8\u7AEF\u4E8C\u7EA7\u83DC\u5355\u5FC5\u987B\u4F5C\u4E3A tab\uFF1A{name}",
|
|
132
129
|
"core.navigation.error.tab_count": "tabBar \u53EA\u80FD\u914D\u7F6E 0 \u6216 2-5 \u9879",
|
|
133
130
|
"core.navigation.error.unique": "\u83DC\u5355 id\u3001name\u3001path \u5FC5\u987B\u552F\u4E00",
|
|
@@ -267,13 +264,10 @@ var en_US_default = {
|
|
|
267
264
|
"core.navigation.error.child_tab": "Only second-level app menus can be tabs: {name}",
|
|
268
265
|
"core.navigation.error.cycle": "Menu parent cycle detected: {name}",
|
|
269
266
|
"core.navigation.error.identity": "Menu id, name, or app/ path is invalid",
|
|
270
|
-
"core.navigation.error.item_object": "Menu item is not an object",
|
|
271
|
-
"core.navigation.error.list_missing": "Menu response does not contain a flat list",
|
|
272
267
|
"core.navigation.error.name_prefix": "Menu name must start with App: {name}",
|
|
273
268
|
"core.navigation.error.parent_invalid": "Menu parent id is invalid: {name}",
|
|
274
269
|
"core.navigation.error.parent_missing": "Menu parent id is missing: {name}",
|
|
275
270
|
"core.navigation.error.parent_not_found": "Menu parent was not found: {name}",
|
|
276
|
-
"core.navigation.error.response_object": "Menu response is not an object",
|
|
277
271
|
"core.navigation.error.root_tab": "Second-level app menu must be a tab: {name}",
|
|
278
272
|
"core.navigation.error.tab_count": "tabBar must contain 0 or 2-5 items",
|
|
279
273
|
"core.navigation.error.unique": "Menu id, name, and path must be unique",
|
|
@@ -413,13 +407,10 @@ var ja_JP_default = {
|
|
|
413
407
|
"core.navigation.error.child_tab": "\u30E2\u30D0\u30A4\u30EB \u30BB\u30AB\u30F3\u30C0\u30EA \u30E1\u30CB\u30E5\u30FC\u306E\u307F\u304C\u30BF\u30D6\u3068\u3057\u3066\u4F7F\u7528\u3067\u304D\u307E\u3059: {name}",
|
|
414
408
|
"core.navigation.error.cycle": "\u30E2\u30D0\u30A4\u30EB \u30E1\u30CB\u30E5\u30FC\u306B\u306F\u30EB\u30FC\u30D7\u306E\u89AA\u304C\u3042\u308A\u307E\u3059: {name}",
|
|
415
409
|
"core.navigation.error.identity": "\u30E1\u30CB\u30E5\u30FC ID\u3001\u540D\u524D\u3001\u307E\u305F\u306F\u30A2\u30D7\u30EA/\u30D1\u30B9\u304C\u7121\u52B9\u3067\u3059",
|
|
416
|
-
"core.navigation.error.item_object": "\u30E1\u30CB\u30E5\u30FC\u9805\u76EE\u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
|
|
417
|
-
"core.navigation.error.list_missing": "\u30E1\u30CB\u30E5\u30FC\u5FDC\u7B54\u306B\u30D5\u30E9\u30C3\u30C8 \u30EA\u30B9\u30C8\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
418
410
|
"core.navigation.error.name_prefix": "\u30E1\u30CB\u30E5\u30FC\u540D\u306B\u306F\u30A2\u30D7\u30EA\u306E\u63A5\u982D\u8F9E\u3092\u4F7F\u7528\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: {name}",
|
|
419
411
|
"core.navigation.error.parent_invalid": "\u30E2\u30D0\u30A4\u30EB \u30E1\u30CB\u30E5\u30FC\u306E\u89AA\u756A\u53F7\u304C\u7121\u52B9\u3067\u3059: {name}",
|
|
420
412
|
"core.navigation.error.parent_missing": "\u30E1\u30CB\u30E5\u30FC\u306B\u89AA\u756A\u53F7\u304C\u3042\u308A\u307E\u305B\u3093: {name}",
|
|
421
413
|
"core.navigation.error.parent_not_found": "\u30E2\u30D0\u30A4\u30EB \u30E1\u30CB\u30E5\u30FC\u306E\u89AA\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: {name}",
|
|
422
|
-
"core.navigation.error.response_object": "\u30E1\u30CB\u30E5\u30FC\u5FDC\u7B54\u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
|
|
423
414
|
"core.navigation.error.root_tab": "\u30E2\u30D0\u30A4\u30EB \u30BB\u30AB\u30F3\u30C0\u30EA \u30E1\u30CB\u30E5\u30FC\u306F\u30BF\u30D6\u3068\u3057\u3066\u4F7F\u7528\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059: {name}",
|
|
424
415
|
"core.navigation.error.tab_count": "tabBar \u306F 0 \u307E\u305F\u306F 2 \uFF5E 5 \u500B\u306E\u9805\u76EE\u306E\u307F\u3092\u8A2D\u5B9A\u3067\u304D\u307E\u3059",
|
|
425
416
|
"core.navigation.error.unique": "\u30E1\u30CB\u30E5\u30FC ID\u3001\u540D\u524D\u3001\u30D1\u30B9\u306F\u4E00\u610F\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059",
|
|
@@ -559,13 +550,10 @@ var zh_TW_default = {
|
|
|
559
550
|
"core.navigation.error.child_tab": "\u53EA\u6709\u79FB\u52D5\u7AEF\u4E8C\u7D1A\u9078\u55AE\u53EF\u4EE5\u4F5C\u70BA tab\uFF1A{name}",
|
|
560
551
|
"core.navigation.error.cycle": "\u79FB\u52D5\u7AEF\u9078\u55AE\u5B58\u5728\u8FF4\u5708\u7236\u7D1A\uFF1A{name}",
|
|
561
552
|
"core.navigation.error.identity": "\u9078\u55AE id\u3001name \u6216 app/ \u8DEF\u5F91\u7121\u6548",
|
|
562
|
-
"core.navigation.error.item_object": "\u9078\u55AE\u9805\u4E0D\u662F\u7269\u4EF6",
|
|
563
|
-
"core.navigation.error.list_missing": "\u9078\u55AE\u97FF\u61C9\u7F3A\u5C11\u6241\u5E73\u5217\u8868",
|
|
564
553
|
"core.navigation.error.name_prefix": "\u9078\u55AE\u540D\u7A31\u5FC5\u9808\u4F7F\u7528 App \u5B57\u9996\uFF1A{name}",
|
|
565
554
|
"core.navigation.error.parent_invalid": "\u79FB\u52D5\u7AEF\u9078\u55AE\u7236\u7D1A\u7DE8\u865F\u7121\u6548\uFF1A{name}",
|
|
566
555
|
"core.navigation.error.parent_missing": "\u9078\u55AE\u7F3A\u5C11\u7236\u7D1A\u7DE8\u865F\uFF1A{name}",
|
|
567
556
|
"core.navigation.error.parent_not_found": "\u79FB\u52D5\u7AEF\u9078\u55AE\u7236\u7D1A\u4E0D\u5B58\u5728\uFF1A{name}",
|
|
568
|
-
"core.navigation.error.response_object": "\u9078\u55AE\u97FF\u61C9\u4E0D\u662F\u7269\u4EF6",
|
|
569
557
|
"core.navigation.error.root_tab": "\u79FB\u52D5\u7AEF\u4E8C\u7D1A\u9078\u55AE\u5FC5\u9808\u4F5C\u70BA tab\uFF1A{name}",
|
|
570
558
|
"core.navigation.error.tab_count": "tabBar \u53EA\u80FD\u914D\u7F6E 0 \u6216 2-5 \u9805",
|
|
571
559
|
"core.navigation.error.unique": "\u9078\u55AE id\u3001name\u3001path \u5FC5\u9808\u552F\u4E00",
|
package/dist/vite.mjs
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
readFileSync,
|
|
7
7
|
readdirSync,
|
|
8
8
|
rmdirSync,
|
|
9
|
-
rmSync,
|
|
10
9
|
statSync,
|
|
11
10
|
unlinkSync,
|
|
12
11
|
writeFileSync
|
|
@@ -284,12 +283,9 @@ function restoreBuildTransaction(transaction, transactionFile) {
|
|
|
284
283
|
removeEmptyParents(dirname(file), transaction.inputDir);
|
|
285
284
|
}
|
|
286
285
|
removeGeneratedStaticFiles(
|
|
287
|
-
transaction.generatedStaticFiles
|
|
286
|
+
transaction.generatedStaticFiles,
|
|
288
287
|
resolve(transaction.inputDir, "static")
|
|
289
288
|
);
|
|
290
|
-
if (transaction.generatedStatic && transaction.generatedStaticFiles === void 0) {
|
|
291
|
-
rmSync(resolve(transaction.inputDir, "static"), { recursive: true, force: true });
|
|
292
|
-
}
|
|
293
289
|
writeFileSync(transaction.pagesFile, transaction.originalPages);
|
|
294
290
|
if (existsSync(transactionFile))
|
|
295
291
|
unlinkSync(transactionFile);
|
|
@@ -505,10 +501,13 @@ function appendPage(manifest, page) {
|
|
|
505
501
|
function createPageWrapper(page) {
|
|
506
502
|
const source = page.source.replace(/\\/g, "/");
|
|
507
503
|
return `<script setup lang="ts">
|
|
504
|
+
import { onShow } from '@dcloudio/uni-app'
|
|
505
|
+
import { setAppPageTitle } from '@liujitcn/kratos-uni-app-core'
|
|
508
506
|
import KratosPage from ${JSON.stringify(source)}
|
|
509
507
|
import KratosTabBar from '@liujitcn/kratos-uni-app-core/components/KratosTabBar.vue'
|
|
510
508
|
|
|
511
509
|
defineOptions({ inheritAttrs: false })
|
|
510
|
+
onShow(() => setAppPageTitle(${JSON.stringify(page.route)}))
|
|
512
511
|
</script>
|
|
513
512
|
|
|
514
513
|
<template>
|
package/package.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { http } from '
|
|
2
|
-
import type {
|
|
1
|
+
import { http } from '../../../utils/http'
|
|
2
|
+
import type {
|
|
3
|
+
ConfigService,
|
|
4
|
+
GetConfigRequest,
|
|
5
|
+
GetConfigResponse,
|
|
6
|
+
} from '../../../rpc/base/v1/config'
|
|
3
7
|
|
|
4
8
|
const CONFIG_URL = '/v1/base/config'
|
|
5
9
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { http } from '
|
|
2
|
-
import type {
|
|
1
|
+
import { http } from '../../../utils/http'
|
|
2
|
+
import type {
|
|
3
|
+
DownloadFileRequest,
|
|
4
|
+
MultiUploadFileResponse,
|
|
5
|
+
FileInfo,
|
|
6
|
+
} from '../../../rpc/base/v1/file'
|
|
3
7
|
|
|
4
8
|
const FILE_URL = '/v1/base/file'
|
|
5
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { http } from '
|
|
1
|
+
import { http } from '../../../utils/http'
|
|
2
2
|
import type {
|
|
3
3
|
OptionLanguageRequest,
|
|
4
4
|
OptionLanguageResponse,
|
|
5
5
|
LanguageService,
|
|
6
|
-
} from '
|
|
6
|
+
} from '../../../rpc/base/v1/language'
|
|
7
7
|
|
|
8
8
|
const LANGUAGE_URL = '/v1/base/language'
|
|
9
9
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { http } from '
|
|
1
|
+
import { http } from '../../../utils/http'
|
|
2
2
|
import type {
|
|
3
3
|
CaptchaRequest,
|
|
4
4
|
CaptchaResponse,
|
|
@@ -12,8 +12,8 @@ import type {
|
|
|
12
12
|
VerifyCaptchaRequest,
|
|
13
13
|
VerifyCaptchaResponse,
|
|
14
14
|
LoginService,
|
|
15
|
-
} from '
|
|
16
|
-
import type { Empty } from '
|
|
15
|
+
} from '../../../rpc/base/v1/login'
|
|
16
|
+
import type { Empty } from '../../../rpc/google/protobuf/empty'
|
|
17
17
|
|
|
18
18
|
const CAPTCHA_URL = '/v1/base/captcha'
|
|
19
19
|
const CAPTCHA_VERIFY_URL = '/v1/base/captcha/verify'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { http } from '
|
|
2
|
-
import type { LoginResponse } from '
|
|
1
|
+
import { http } from '../../../utils/http'
|
|
2
|
+
import type { LoginResponse } from '../../../rpc/base/v1/login'
|
|
3
3
|
import type {
|
|
4
4
|
BeginMfaDisableRequest,
|
|
5
5
|
BeginMfaDisableResponse,
|
|
@@ -18,8 +18,8 @@ import type {
|
|
|
18
18
|
RecoveryCodesResponse,
|
|
19
19
|
RegenerateMfaRecoveryCodesRequest,
|
|
20
20
|
VerifyMfaRequest,
|
|
21
|
-
} from '
|
|
22
|
-
import type { Empty } from '
|
|
21
|
+
} from '../../../rpc/base/v1/mfa'
|
|
22
|
+
import type { Empty } from '../../../rpc/google/protobuf/empty'
|
|
23
23
|
|
|
24
24
|
const MFA_URL = '/v1/base/mfa'
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { http } from '
|
|
1
|
+
import { http } from '../../../utils/http'
|
|
2
2
|
import type {
|
|
3
3
|
CreateOauthAuthorizationRequest,
|
|
4
4
|
CreateOauthAuthorizationResponse,
|
|
@@ -19,8 +19,8 @@ import type {
|
|
|
19
19
|
ListOauthProviderResponse,
|
|
20
20
|
OauthService,
|
|
21
21
|
UnbindOauthAccountRequest,
|
|
22
|
-
} from '
|
|
23
|
-
import type { Empty } from '
|
|
22
|
+
} from '../../../rpc/base/v1/oauth'
|
|
23
|
+
import type { Empty } from '../../../rpc/google/protobuf/empty'
|
|
24
24
|
|
|
25
25
|
const OAUTH_PROVIDER_URL = '/v1/base/oauth/provider'
|
|
26
26
|
const OAUTH_AUTHORIZATION_URL = '/v1/base/oauth/authorization'
|
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
import { http } from '
|
|
1
|
+
import { http } from '../../../../utils/http'
|
|
2
2
|
import {
|
|
3
3
|
type AuthService,
|
|
4
4
|
type BindUserPhoneRequest,
|
|
5
5
|
type BindUserPhoneResponse,
|
|
6
|
+
type GetUserProfileRequest,
|
|
7
|
+
type UpdateUserProfileRequest,
|
|
6
8
|
type UserProfileForm,
|
|
7
|
-
} from '
|
|
8
|
-
import type { Empty } from '
|
|
9
|
+
} from '../../../../rpc/system/app/v1/auth'
|
|
10
|
+
import type { Empty } from '../../../../rpc/google/protobuf/empty'
|
|
9
11
|
|
|
10
12
|
const AUTH_URL = '/v1/app/auth'
|
|
11
13
|
|
|
12
|
-
/** 获取用户资料请求兼容空请求结构。 */
|
|
13
|
-
type GetUserProfileRequestCompat = Empty
|
|
14
|
-
|
|
15
|
-
/** 更新用户资料请求兼容新旧两种表单包裹方式。 */
|
|
16
|
-
type UpdateUserProfileRequestCompat = Partial<UserProfileForm> & {
|
|
17
|
-
user_profile?: UserProfileForm
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
/** 用户认证资料服务 */
|
|
21
15
|
export class AuthServiceImpl implements AuthService {
|
|
22
16
|
/** 获取已登录用户资料 */
|
|
23
|
-
GetUserProfile(request:
|
|
17
|
+
GetUserProfile(request: GetUserProfileRequest): Promise<UserProfileForm> {
|
|
24
18
|
return http<UserProfileForm>({
|
|
25
19
|
url: `${AUTH_URL}/profile`,
|
|
26
20
|
method: 'GET',
|
|
@@ -30,13 +24,12 @@ export class AuthServiceImpl implements AuthService {
|
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
/** 修改个人中心用户信息 */
|
|
33
|
-
UpdateUserProfile(request:
|
|
34
|
-
const userProfile = request.user_profile ?? (request as UserProfileForm)
|
|
27
|
+
UpdateUserProfile(request: UpdateUserProfileRequest): Promise<Empty> {
|
|
35
28
|
return http<Empty>({
|
|
36
29
|
url: `${AUTH_URL}/profile`,
|
|
37
30
|
method: 'PUT',
|
|
38
31
|
authMode: 'required',
|
|
39
|
-
data:
|
|
32
|
+
data: request.user_profile,
|
|
40
33
|
})
|
|
41
34
|
}
|
|
42
35
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { http } from '
|
|
1
|
+
import { http } from '../../../../utils/http'
|
|
2
2
|
import type {
|
|
3
3
|
BaseAreaService,
|
|
4
4
|
TreeBaseAreaRequest,
|
|
5
5
|
TreeBaseAreaResponse,
|
|
6
|
-
} from '
|
|
6
|
+
} from '../../../../rpc/system/app/v1/base_area'
|
|
7
7
|
|
|
8
8
|
const BASE_AREA_URL = '/v1/app/base/area'
|
|
9
9
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { http } from '../../../../utils/http'
|
|
2
|
+
import type {
|
|
3
|
+
BaseDictForm,
|
|
4
|
+
BaseDictService,
|
|
5
|
+
GetBaseDictRequest,
|
|
6
|
+
} from '../../../../rpc/system/app/v1/base_dict'
|
|
7
|
+
|
|
8
|
+
const BASE_DICT_URL = '/v1/app/base/dict'
|
|
9
|
+
|
|
10
|
+
/** 字典服务 */
|
|
11
|
+
export class BaseDictServiceImpl implements BaseDictService {
|
|
12
|
+
/** 查询单个字典 */
|
|
13
|
+
GetBaseDict(request: GetBaseDictRequest): Promise<BaseDictForm> {
|
|
14
|
+
return http<BaseDictForm>({
|
|
15
|
+
url: `${BASE_DICT_URL}/${request.code}`,
|
|
16
|
+
method: 'GET',
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const defBaseDictService = new BaseDictServiceImpl()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { http } from '
|
|
2
|
-
import type { ListBaseMenuResponse } from '
|
|
1
|
+
import { http } from '../../../../utils/http'
|
|
2
|
+
import type { ListBaseMenuResponse } from '../../../../rpc/system/app/v1/base_menu'
|
|
3
3
|
|
|
4
4
|
/** 远端移动菜单服务。 */
|
|
5
5
|
export class BaseMenuServiceImpl {
|
package/src/bootstrap.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from './locales'
|
|
10
10
|
import { initializeAppNavigation } from './navigation'
|
|
11
11
|
import { useSettingStore } from './stores'
|
|
12
|
-
import { defLanguageService } from './api/base/language'
|
|
12
|
+
import { defLanguageService } from './api/base/v1/language'
|
|
13
13
|
|
|
14
14
|
/** uni-app 启动参数。 */
|
|
15
15
|
export interface KratosAppBootstrapOptions {
|
|
@@ -3,7 +3,7 @@ import { computed, onMounted, ref, watch } from 'vue'
|
|
|
3
3
|
import { useUserStore } from '../stores'
|
|
4
4
|
import { navigateToLogin } from '../utils/navigation'
|
|
5
5
|
import { getLanguageOptions, useI18n, type SupportedLocale } from '../locales'
|
|
6
|
-
import { defMfaService } from '../api/base/mfa'
|
|
6
|
+
import { defMfaService } from '../api/base/v1/mfa'
|
|
7
7
|
import MfaManageDialog from './MfaManageDialog.vue'
|
|
8
8
|
|
|
9
9
|
defineOptions({ name: 'KratosSettingsPage' })
|
|
@@ -240,12 +240,24 @@ const onLogout = () => {
|
|
|
240
240
|
|
|
241
241
|
.locale-item {
|
|
242
242
|
display: flex;
|
|
243
|
+
min-width: 0;
|
|
243
244
|
justify-content: space-between;
|
|
244
245
|
padding-right: 42rpx;
|
|
246
|
+
|
|
247
|
+
> text:first-child {
|
|
248
|
+
flex-shrink: 0;
|
|
249
|
+
}
|
|
245
250
|
}
|
|
246
251
|
|
|
247
252
|
.locale-value {
|
|
253
|
+
flex: 1;
|
|
254
|
+
min-width: 0;
|
|
255
|
+
overflow: hidden;
|
|
256
|
+
margin-left: 24rpx;
|
|
248
257
|
color: #888;
|
|
258
|
+
text-align: right;
|
|
259
|
+
text-overflow: ellipsis;
|
|
260
|
+
white-space: nowrap;
|
|
249
261
|
}
|
|
250
262
|
|
|
251
263
|
.mfa-item {
|
|
@@ -24,9 +24,11 @@ const messageBadge = useAppMenuBadge('MESSAGE_INBOX')
|
|
|
24
24
|
class="kratos-tab-bar__item"
|
|
25
25
|
@tap="navigate(item.path)"
|
|
26
26
|
>
|
|
27
|
-
<
|
|
28
|
-
messageBadge
|
|
29
|
-
|
|
27
|
+
<view
|
|
28
|
+
v-if="item.viewKey === 'MESSAGE_INBOX' && messageBadge"
|
|
29
|
+
class="kratos-tab-bar__badge"
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
/>
|
|
30
32
|
<image
|
|
31
33
|
v-if="resolveModuleIcon(item.icon)"
|
|
32
34
|
class="kratos-tab-bar__icon"
|
|
@@ -84,24 +86,46 @@ const messageBadge = useAppMenuBadge('MESSAGE_INBOX')
|
|
|
84
86
|
.kratos-tab-bar__message-icon {
|
|
85
87
|
position: relative;
|
|
86
88
|
width: 22px;
|
|
87
|
-
height:
|
|
88
|
-
margin-bottom:
|
|
89
|
+
height: 18px;
|
|
90
|
+
margin-bottom: 6px;
|
|
89
91
|
border: 2px solid #9aa4b2;
|
|
90
|
-
border-radius:
|
|
92
|
+
border-radius: 6px;
|
|
91
93
|
box-sizing: border-box;
|
|
92
94
|
}
|
|
93
|
-
.kratos-tab-bar__message-icon::
|
|
95
|
+
.kratos-tab-bar__message-icon::before {
|
|
94
96
|
position: absolute;
|
|
95
|
-
top:
|
|
97
|
+
top: 5px;
|
|
96
98
|
left: 4px;
|
|
97
|
-
width:
|
|
98
|
-
height:
|
|
99
|
+
width: 3px;
|
|
100
|
+
height: 3px;
|
|
101
|
+
border-radius: 50%;
|
|
102
|
+
background: #9aa4b2;
|
|
103
|
+
box-shadow:
|
|
104
|
+
5px 0 #9aa4b2,
|
|
105
|
+
10px 0 #9aa4b2;
|
|
106
|
+
content: '';
|
|
107
|
+
}
|
|
108
|
+
.kratos-tab-bar__message-icon::after {
|
|
109
|
+
position: absolute;
|
|
110
|
+
bottom: -5px;
|
|
111
|
+
left: 3px;
|
|
112
|
+
width: 7px;
|
|
113
|
+
height: 7px;
|
|
99
114
|
border-right: 2px solid #9aa4b2;
|
|
100
115
|
border-bottom: 2px solid #9aa4b2;
|
|
116
|
+
background: #fff;
|
|
101
117
|
content: '';
|
|
102
118
|
transform: rotate(45deg);
|
|
103
119
|
}
|
|
104
|
-
.kratos-tab-bar__message-icon--active
|
|
120
|
+
.kratos-tab-bar__message-icon--active {
|
|
121
|
+
border-color: #27ba9b;
|
|
122
|
+
}
|
|
123
|
+
.kratos-tab-bar__message-icon--active::before {
|
|
124
|
+
background: #27ba9b;
|
|
125
|
+
box-shadow:
|
|
126
|
+
5px 0 #27ba9b,
|
|
127
|
+
10px 0 #27ba9b;
|
|
128
|
+
}
|
|
105
129
|
.kratos-tab-bar__message-icon--active::after {
|
|
106
130
|
border-color: #27ba9b;
|
|
107
131
|
}
|
|
@@ -110,16 +134,13 @@ const messageBadge = useAppMenuBadge('MESSAGE_INBOX')
|
|
|
110
134
|
}
|
|
111
135
|
.kratos-tab-bar__badge {
|
|
112
136
|
position: absolute;
|
|
113
|
-
top:
|
|
114
|
-
right: calc(50% -
|
|
115
|
-
|
|
116
|
-
height:
|
|
117
|
-
|
|
118
|
-
border
|
|
137
|
+
top: 0;
|
|
138
|
+
right: calc(50% - 19px);
|
|
139
|
+
width: 10px;
|
|
140
|
+
height: 10px;
|
|
141
|
+
box-sizing: border-box;
|
|
142
|
+
border: 2px solid #fff;
|
|
143
|
+
border-radius: 50%;
|
|
119
144
|
background: #e5484d;
|
|
120
|
-
color: #fff;
|
|
121
|
-
font-size: 9px;
|
|
122
|
-
line-height: 14px;
|
|
123
|
-
text-align: center;
|
|
124
145
|
}
|
|
125
146
|
</style>
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import { createWebAuthnCredential, getWebAuthnAssertion } from '../utils/webauthn'
|
|
11
11
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from '../utils/passwordCrypto'
|
|
12
12
|
import { useI18n } from '../locales'
|
|
13
|
-
import { defMfaService } from '../api/base/mfa'
|
|
13
|
+
import { defMfaService } from '../api/base/v1/mfa'
|
|
14
14
|
|
|
15
15
|
const props = withDefaults(
|
|
16
16
|
defineProps<{
|
package/src/locales/en-US.json
CHANGED
|
@@ -95,13 +95,10 @@
|
|
|
95
95
|
"core.navigation.error.child_tab": "Only second-level app menus can be tabs: {name}",
|
|
96
96
|
"core.navigation.error.cycle": "Menu parent cycle detected: {name}",
|
|
97
97
|
"core.navigation.error.identity": "Menu id, name, or app/ path is invalid",
|
|
98
|
-
"core.navigation.error.item_object": "Menu item is not an object",
|
|
99
|
-
"core.navigation.error.list_missing": "Menu response does not contain a flat list",
|
|
100
98
|
"core.navigation.error.name_prefix": "Menu name must start with App: {name}",
|
|
101
99
|
"core.navigation.error.parent_invalid": "Menu parent id is invalid: {name}",
|
|
102
100
|
"core.navigation.error.parent_missing": "Menu parent id is missing: {name}",
|
|
103
101
|
"core.navigation.error.parent_not_found": "Menu parent was not found: {name}",
|
|
104
|
-
"core.navigation.error.response_object": "Menu response is not an object",
|
|
105
102
|
"core.navigation.error.root_tab": "Second-level app menu must be a tab: {name}",
|
|
106
103
|
"core.navigation.error.tab_count": "tabBar must contain 0 or 2-5 items",
|
|
107
104
|
"core.navigation.error.unique": "Menu id, name, and path must be unique",
|
package/src/locales/ja-JP.json
CHANGED
|
@@ -95,13 +95,10 @@
|
|
|
95
95
|
"core.navigation.error.child_tab": "モバイル セカンダリ メニューのみがタブとして使用できます: {name}",
|
|
96
96
|
"core.navigation.error.cycle": "モバイル メニューにはループの親があります: {name}",
|
|
97
97
|
"core.navigation.error.identity": "メニュー ID、名前、またはアプリ/パスが無効です",
|
|
98
|
-
"core.navigation.error.item_object": "メニュー項目はオブジェクトではありません",
|
|
99
|
-
"core.navigation.error.list_missing": "メニュー応答にフラット リストがありません",
|
|
100
98
|
"core.navigation.error.name_prefix": "メニュー名にはアプリの接頭辞を使用する必要があります: {name}",
|
|
101
99
|
"core.navigation.error.parent_invalid": "モバイル メニューの親番号が無効です: {name}",
|
|
102
100
|
"core.navigation.error.parent_missing": "メニューに親番号がありません: {name}",
|
|
103
101
|
"core.navigation.error.parent_not_found": "モバイル メニューの親が存在しません: {name}",
|
|
104
|
-
"core.navigation.error.response_object": "メニュー応答はオブジェクトではありません",
|
|
105
102
|
"core.navigation.error.root_tab": "モバイル セカンダリ メニューはタブとして使用する必要があります: {name}",
|
|
106
103
|
"core.navigation.error.tab_count": "tabBar は 0 または 2 ~ 5 個の項目のみを設定できます",
|
|
107
104
|
"core.navigation.error.unique": "メニュー ID、名前、パスは一意である必要があります",
|
package/src/locales/zh-CN.json
CHANGED
|
@@ -95,13 +95,10 @@
|
|
|
95
95
|
"core.navigation.error.child_tab": "只有移动端二级菜单可以作为 tab:{name}",
|
|
96
96
|
"core.navigation.error.cycle": "移动端菜单存在循环父级:{name}",
|
|
97
97
|
"core.navigation.error.identity": "菜单 id、name 或 app/ 路径无效",
|
|
98
|
-
"core.navigation.error.item_object": "菜单项不是对象",
|
|
99
|
-
"core.navigation.error.list_missing": "菜单响应缺少扁平列表",
|
|
100
98
|
"core.navigation.error.name_prefix": "菜单名称必须使用 App 前缀:{name}",
|
|
101
99
|
"core.navigation.error.parent_invalid": "移动端菜单父级编号无效:{name}",
|
|
102
100
|
"core.navigation.error.parent_missing": "菜单缺少父级编号:{name}",
|
|
103
101
|
"core.navigation.error.parent_not_found": "移动端菜单父级不存在:{name}",
|
|
104
|
-
"core.navigation.error.response_object": "菜单响应不是对象",
|
|
105
102
|
"core.navigation.error.root_tab": "移动端二级菜单必须作为 tab:{name}",
|
|
106
103
|
"core.navigation.error.tab_count": "tabBar 只能配置 0 或 2-5 项",
|
|
107
104
|
"core.navigation.error.unique": "菜单 id、name、path 必须唯一",
|
package/src/locales/zh-TW.json
CHANGED
|
@@ -95,13 +95,10 @@
|
|
|
95
95
|
"core.navigation.error.child_tab": "只有移動端二級選單可以作為 tab:{name}",
|
|
96
96
|
"core.navigation.error.cycle": "移動端選單存在迴圈父級:{name}",
|
|
97
97
|
"core.navigation.error.identity": "選單 id、name 或 app/ 路徑無效",
|
|
98
|
-
"core.navigation.error.item_object": "選單項不是物件",
|
|
99
|
-
"core.navigation.error.list_missing": "選單響應缺少扁平列表",
|
|
100
98
|
"core.navigation.error.name_prefix": "選單名稱必須使用 App 字首:{name}",
|
|
101
99
|
"core.navigation.error.parent_invalid": "移動端選單父級編號無效:{name}",
|
|
102
100
|
"core.navigation.error.parent_missing": "選單缺少父級編號:{name}",
|
|
103
101
|
"core.navigation.error.parent_not_found": "移動端選單父級不存在:{name}",
|
|
104
|
-
"core.navigation.error.response_object": "選單響應不是物件",
|
|
105
102
|
"core.navigation.error.root_tab": "移動端二級選單必須作為 tab:{name}",
|
|
106
103
|
"core.navigation.error.tab_count": "tabBar 只能配置 0 或 2-5 項",
|
|
107
104
|
"core.navigation.error.unique": "選單 id、name、path 必須唯一",
|
package/src/navigation.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computed, readonly, ref } from 'vue'
|
|
2
|
-
import { defBaseMenuService } from './api/
|
|
2
|
+
import { defBaseMenuService } from './api/system/app/v1/base_menu'
|
|
3
|
+
import type { BaseMenu, ListBaseMenuResponse } from './rpc/system/app/v1/base_menu'
|
|
3
4
|
import { hasValidToken } from './utils/auth'
|
|
4
5
|
import { navigateToLogin } from './utils/navigation'
|
|
5
6
|
import { resolveStaticView } from './module'
|
|
@@ -32,7 +33,7 @@ export interface AppMenuNode extends AppMenu {
|
|
|
32
33
|
/** 菜单获取适配器。 */
|
|
33
34
|
export interface AppNavigationAdapter {
|
|
34
35
|
/** 获取扁平移动菜单配置。 */
|
|
35
|
-
list(): Promise<
|
|
36
|
+
list(): Promise<ListBaseMenuResponse>
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/** 逻辑路由解析结果。 */
|
|
@@ -45,10 +46,10 @@ export interface ResolvedAppRoute {
|
|
|
45
46
|
const ANONYMOUS_CACHE_KEY = 'kratos-uni-app:navigation:anonymous'
|
|
46
47
|
const AUTHENTICATED_CACHE_KEY = 'kratos-uni-app:navigation:authenticated'
|
|
47
48
|
/** 固定移动端菜单根目录编号。 */
|
|
48
|
-
export const APP_MENU_ROOT_ID =
|
|
49
|
+
export const APP_MENU_ROOT_ID = 99000000
|
|
49
50
|
const createDefaultMenus = (): AppMenu[] => [
|
|
50
51
|
{
|
|
51
|
-
id:
|
|
52
|
+
id: 99010000,
|
|
52
53
|
parentId: APP_MENU_ROOT_ID,
|
|
53
54
|
name: 'AppHome',
|
|
54
55
|
path: 'app/home',
|
|
@@ -60,7 +61,7 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
60
61
|
selectedIcon: 'HOME_SELECTED',
|
|
61
62
|
},
|
|
62
63
|
{
|
|
63
|
-
id:
|
|
64
|
+
id: 99040000,
|
|
64
65
|
parentId: APP_MENU_ROOT_ID,
|
|
65
66
|
name: 'AppMessage',
|
|
66
67
|
path: 'app/message',
|
|
@@ -72,7 +73,7 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
72
73
|
selectedIcon: 'MESSAGE_SELECTED',
|
|
73
74
|
},
|
|
74
75
|
{
|
|
75
|
-
id:
|
|
76
|
+
id: 99090000,
|
|
76
77
|
parentId: APP_MENU_ROOT_ID,
|
|
77
78
|
name: 'AppMy',
|
|
78
79
|
path: 'app/my',
|
|
@@ -84,8 +85,8 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
84
85
|
selectedIcon: 'USER_SELECTED',
|
|
85
86
|
},
|
|
86
87
|
{
|
|
87
|
-
id:
|
|
88
|
-
parentId:
|
|
88
|
+
id: 99010100,
|
|
89
|
+
parentId: 99010000,
|
|
89
90
|
name: 'AppLogin',
|
|
90
91
|
path: 'app/login',
|
|
91
92
|
viewKey: 'LOGIN',
|
|
@@ -94,8 +95,8 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
94
95
|
inTabBar: false,
|
|
95
96
|
},
|
|
96
97
|
{
|
|
97
|
-
id:
|
|
98
|
-
parentId:
|
|
98
|
+
id: 99010101,
|
|
99
|
+
parentId: 99010100,
|
|
99
100
|
name: 'AppProtocol',
|
|
100
101
|
path: 'app/protocol/:type',
|
|
101
102
|
viewKey: 'PROTOCOL',
|
|
@@ -104,8 +105,8 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
104
105
|
inTabBar: false,
|
|
105
106
|
},
|
|
106
107
|
{
|
|
107
|
-
id:
|
|
108
|
-
parentId:
|
|
108
|
+
id: 99090100,
|
|
109
|
+
parentId: 99090000,
|
|
109
110
|
name: 'AppProfile',
|
|
110
111
|
path: 'app/profile',
|
|
111
112
|
viewKey: 'PROFILE',
|
|
@@ -114,8 +115,8 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
114
115
|
inTabBar: false,
|
|
115
116
|
},
|
|
116
117
|
{
|
|
117
|
-
id:
|
|
118
|
-
parentId:
|
|
118
|
+
id: 99090200,
|
|
119
|
+
parentId: 99090000,
|
|
119
120
|
name: 'AppSettings',
|
|
120
121
|
path: 'app/settings',
|
|
121
122
|
viewKey: 'SETTINGS',
|
|
@@ -124,8 +125,8 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
124
125
|
inTabBar: false,
|
|
125
126
|
},
|
|
126
127
|
{
|
|
127
|
-
id:
|
|
128
|
-
parentId:
|
|
128
|
+
id: 99090300,
|
|
129
|
+
parentId: 99090000,
|
|
129
130
|
name: 'AppAi',
|
|
130
131
|
path: 'app/ai',
|
|
131
132
|
viewKey: 'AI',
|
|
@@ -134,8 +135,8 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
134
135
|
inTabBar: false,
|
|
135
136
|
},
|
|
136
137
|
{
|
|
137
|
-
id:
|
|
138
|
-
parentId:
|
|
138
|
+
id: 99010200,
|
|
139
|
+
parentId: 99010000,
|
|
139
140
|
name: 'AppWebView',
|
|
140
141
|
path: 'app/webview',
|
|
141
142
|
viewKey: 'WEBVIEW',
|
|
@@ -148,12 +149,25 @@ const createDefaultMenus = (): AppMenu[] => [
|
|
|
148
149
|
const menus = ref<AppMenu[]>([])
|
|
149
150
|
const ready = ref(false)
|
|
150
151
|
const appMenuBadges = ref<Record<string, number>>({})
|
|
152
|
+
const isH5Runtime = typeof window !== 'undefined'
|
|
151
153
|
let tabNavigationTarget: string | undefined
|
|
152
154
|
let adapter: AppNavigationAdapter = {
|
|
153
155
|
list: () => defBaseMenuService.ListBaseMenu(),
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
const nativeTabViewKeys = ['HOME', 'PROFILE_HOME']
|
|
159
|
+
const pageTitleKeys: Record<string, string> = {
|
|
160
|
+
'pages/index/index': 'core.home.main_title',
|
|
161
|
+
'pages/login/login': 'common.action.login',
|
|
162
|
+
'pages/login/protocal': 'core.navigation.protocol',
|
|
163
|
+
'pages/my/my': 'core.navigation.my',
|
|
164
|
+
'pagesMember/ai/index': 'system.ai.chat_title',
|
|
165
|
+
'pagesMember/message/detail': 'system.notification.title',
|
|
166
|
+
'pagesMember/message/index': 'system.notification.title',
|
|
167
|
+
'pagesMember/profile/profile': 'system.profile.title',
|
|
168
|
+
'pagesMember/settings/settings': 'core.settings.title',
|
|
169
|
+
}
|
|
170
|
+
const resetTabViewKeys = ['MESSAGE_INBOX']
|
|
157
171
|
|
|
158
172
|
/** 替换导航远端适配器,供宿主接入自定义契约。 */
|
|
159
173
|
export function setAppNavigationAdapter(nextAdapter: AppNavigationAdapter): void {
|
|
@@ -241,20 +255,29 @@ function navigateTabRoute(url: string): void {
|
|
|
241
255
|
const pages = getCurrentPages()
|
|
242
256
|
const currentIndex = pages.length - 1
|
|
243
257
|
const currentRoute = pages[currentIndex]?.route?.replace(/^\/+/, '')
|
|
244
|
-
if (currentRoute === targetRoute || tabNavigationTarget) return
|
|
258
|
+
if (currentRoute === targetRoute || tabNavigationTarget === targetRoute) return
|
|
245
259
|
|
|
246
260
|
tabNavigationTarget = targetRoute
|
|
247
261
|
const release = () => {
|
|
248
262
|
if (tabNavigationTarget === targetRoute) tabNavigationTarget = undefined
|
|
249
263
|
}
|
|
264
|
+
if (isH5Runtime) {
|
|
265
|
+
uni.reLaunch({ url, success: release, fail: release, complete: release })
|
|
266
|
+
return
|
|
267
|
+
}
|
|
250
268
|
if (nativeTabViewKeys.some((viewKey) => resolveStaticView(viewKey) === targetRoute)) {
|
|
251
269
|
uni.switchTab({
|
|
252
270
|
url,
|
|
253
271
|
success: release,
|
|
254
272
|
fail: () => navigateTabRouteInStack(url, targetRoute, pages, currentIndex, release),
|
|
273
|
+
complete: release,
|
|
255
274
|
})
|
|
256
275
|
return
|
|
257
276
|
}
|
|
277
|
+
if (resetTabViewKeys.some((viewKey) => resolveStaticView(viewKey) === targetRoute)) {
|
|
278
|
+
uni.reLaunch({ url, success: release, fail: release, complete: release })
|
|
279
|
+
return
|
|
280
|
+
}
|
|
258
281
|
navigateTabRouteInStack(url, targetRoute, pages, currentIndex, release)
|
|
259
282
|
}
|
|
260
283
|
|
|
@@ -318,6 +341,15 @@ export function useAppMenuBadge(viewKey: string) {
|
|
|
318
341
|
return computed(() => appMenuBadges.value[viewKey] ?? 0)
|
|
319
342
|
}
|
|
320
343
|
|
|
344
|
+
/** 根据物理页面路由更新 H5 和原生导航栏标题。 */
|
|
345
|
+
export function setAppPageTitle(route: string): void {
|
|
346
|
+
const titleKey = pageTitleKeys[route]
|
|
347
|
+
const menuTitle = menus.value.find((menu) => resolveStaticView(menu.viewKey) === route)?.title
|
|
348
|
+
const title = titleKey ? t(titleKey) : menuTitle
|
|
349
|
+
if (!title) return
|
|
350
|
+
void uni.setNavigationBarTitle({ title })
|
|
351
|
+
}
|
|
352
|
+
|
|
321
353
|
/** 获取导航响应式状态。 */
|
|
322
354
|
export function useAppNavigation() {
|
|
323
355
|
const menuTree = computed<AppMenuNode[]>(() => buildMenuTree(menus.value, APP_MENU_ROOT_ID))
|
|
@@ -347,48 +379,32 @@ function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
|
|
|
347
379
|
const cached = uni.getStorageSync(cacheKey) as unknown
|
|
348
380
|
if (!Array.isArray(cached)) return
|
|
349
381
|
try {
|
|
350
|
-
const
|
|
351
|
-
validateMenus(
|
|
352
|
-
return
|
|
382
|
+
const cachedMenus = cached as AppMenu[]
|
|
383
|
+
validateMenus(cachedMenus)
|
|
384
|
+
return cachedMenus
|
|
353
385
|
} catch {
|
|
354
386
|
uni.removeStorageSync(cacheKey)
|
|
355
387
|
}
|
|
356
388
|
}
|
|
357
389
|
|
|
358
|
-
function normalizeMenuResponse(response:
|
|
359
|
-
|
|
360
|
-
if (!response || typeof response !== 'object')
|
|
361
|
-
throw new Error(t('core.navigation.error.response_object'))
|
|
362
|
-
const record = response as Record<string, unknown>
|
|
363
|
-
const list = record.items ?? record.list ?? record.data
|
|
364
|
-
if (!Array.isArray(list)) throw new Error(t('core.navigation.error.list_missing'))
|
|
365
|
-
return list.map(normalizeMenu)
|
|
390
|
+
function normalizeMenuResponse(response: ListBaseMenuResponse): AppMenu[] {
|
|
391
|
+
return response.items.map(normalizeMenu)
|
|
366
392
|
}
|
|
367
393
|
|
|
368
|
-
function normalizeMenu(
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
const meta = (item.meta ?? {}) as Record<string, unknown>
|
|
372
|
-
const app = (meta.app ?? item.app ?? {}) as Record<string, unknown>
|
|
394
|
+
function normalizeMenu(item: BaseMenu): AppMenu {
|
|
395
|
+
const meta = item.meta
|
|
396
|
+
const app = meta?.app
|
|
373
397
|
return {
|
|
374
|
-
id:
|
|
375
|
-
parentId:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
inTabBar: Boolean(app.in_tab_bar ?? app.inTabBar),
|
|
385
|
-
icon: typeof meta.icon === 'string' ? meta.icon : undefined,
|
|
386
|
-
selectedIcon:
|
|
387
|
-
typeof app.selected_icon === 'string'
|
|
388
|
-
? app.selected_icon
|
|
389
|
-
: typeof app.selectedIcon === 'string'
|
|
390
|
-
? app.selectedIcon
|
|
391
|
-
: undefined,
|
|
398
|
+
id: item.id,
|
|
399
|
+
parentId: item.parent_id,
|
|
400
|
+
name: item.name,
|
|
401
|
+
path: item.path,
|
|
402
|
+
viewKey: app?.view_key ?? '',
|
|
403
|
+
title: meta?.title ?? '',
|
|
404
|
+
access: (app?.access ?? 'AUTHENTICATED') as AppMenuAccess,
|
|
405
|
+
inTabBar: app?.in_tab_bar ?? false,
|
|
406
|
+
icon: meta?.icon,
|
|
407
|
+
selectedIcon: app?.selected_icon,
|
|
392
408
|
}
|
|
393
409
|
}
|
|
394
410
|
|
package/src/rpc/base/v1/login.ts
CHANGED
|
@@ -22,6 +22,8 @@ export enum PasswordCryptoScene {
|
|
|
22
22
|
PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD = 4,
|
|
23
23
|
/** PASSWORD_CRYPTO_SCENE_MFA - 多因素认证敏感操作场景。 */
|
|
24
24
|
PASSWORD_CRYPTO_SCENE_MFA = 5,
|
|
25
|
+
/** PASSWORD_CRYPTO_SCENE_CONFIGURE_PASSWORD_POLICY - 密码策略初始化密码配置场景。 */
|
|
26
|
+
PASSWORD_CRYPTO_SCENE_CONFIGURE_PASSWORD_POLICY = 6,
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -107,7 +109,7 @@ export interface RefreshTokenRequest {
|
|
|
107
109
|
export interface RefreshTokenResponse {
|
|
108
110
|
/** 访问令牌,必选项。 */
|
|
109
111
|
access_token: string;
|
|
110
|
-
/**
|
|
112
|
+
/** 更新令牌;管理端 Cookie-only 请求不在响应体返回,应用端请求可返回。 */
|
|
111
113
|
refresh_token: string;
|
|
112
114
|
/** 令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 */
|
|
113
115
|
token_type: string;
|
|
@@ -135,7 +137,7 @@ export interface LoginRequest {
|
|
|
135
137
|
export interface LoginResponse {
|
|
136
138
|
/** 访问令牌,必选项。 */
|
|
137
139
|
access_token: string;
|
|
138
|
-
/**
|
|
140
|
+
/** 更新令牌;管理端 Cookie-only 请求不在响应体返回,应用端请求可返回。 */
|
|
139
141
|
refresh_token: string;
|
|
140
142
|
/** 令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 */
|
|
141
143
|
token_type: string;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
/* eslint-disable */
|
|
8
8
|
import type { Empty } from "../../../google/protobuf/empty";
|
|
9
|
+
import type { BaseUserIDType } from "../../common/v1/common";
|
|
9
10
|
|
|
10
11
|
/** 获取用户资料请求参数 */
|
|
11
12
|
export interface GetUserProfileRequest {
|
|
@@ -21,6 +22,12 @@ export interface UserProfileForm {
|
|
|
21
22
|
gender: number;
|
|
22
23
|
/** 手机号 */
|
|
23
24
|
phone: string;
|
|
25
|
+
/** 邮箱 */
|
|
26
|
+
email: string;
|
|
27
|
+
/** 证件类型:枚举【BaseUserIDType】 */
|
|
28
|
+
id_type: BaseUserIDType;
|
|
29
|
+
/** 证件号 */
|
|
30
|
+
id_code: string;
|
|
24
31
|
/** 头像 */
|
|
25
32
|
avatar: string;
|
|
26
33
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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: system/common/v1/common.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
|
|
9
|
+
/** 系统公共用户相关枚举。 */
|
|
10
|
+
export enum BaseUserIDType {
|
|
11
|
+
/** BASE_USER_ID_TYPE_UNSPECIFIED - 未指定证件类型。 */
|
|
12
|
+
BASE_USER_ID_TYPE_UNSPECIFIED = 0,
|
|
13
|
+
/** BASE_USER_ID_TYPE_ID_CARD - 居民身份证。 */
|
|
14
|
+
BASE_USER_ID_TYPE_ID_CARD = 1,
|
|
15
|
+
/** BASE_USER_ID_TYPE_PASSPORT - 护照。 */
|
|
16
|
+
BASE_USER_ID_TYPE_PASSPORT = 2,
|
|
17
|
+
/** BASE_USER_ID_TYPE_HK_MACAO_PERMIT - 港澳居民来往内地通行证。 */
|
|
18
|
+
BASE_USER_ID_TYPE_HK_MACAO_PERMIT = 3,
|
|
19
|
+
/** BASE_USER_ID_TYPE_TAIWAN_PERMIT - 台湾居民来往大陆通行证。 */
|
|
20
|
+
BASE_USER_ID_TYPE_TAIWAN_PERMIT = 4,
|
|
21
|
+
/** BASE_USER_ID_TYPE_FOREIGN_PERMANENT_RESIDENCE - 外国人永久居留身份证。 */
|
|
22
|
+
BASE_USER_ID_TYPE_FOREIGN_PERMANENT_RESIDENCE = 5,
|
|
23
|
+
/** BASE_USER_ID_TYPE_OTHER - 其他证件。 */
|
|
24
|
+
BASE_USER_ID_TYPE_OTHER = 6,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 用户密码修改状态。 */
|
|
28
|
+
export enum BaseUserPasswordChangeStatus {
|
|
29
|
+
/** BASE_USER_PASSWORD_CHANGE_STATUS_UNSPECIFIED - 未指定密码修改状态。 */
|
|
30
|
+
BASE_USER_PASSWORD_CHANGE_STATUS_UNSPECIFIED = 0,
|
|
31
|
+
/** BASE_USER_PASSWORD_CHANGE_STATUS_REQUIRED - 当前必须修改密码。 */
|
|
32
|
+
BASE_USER_PASSWORD_CHANGE_STATUS_REQUIRED = 1,
|
|
33
|
+
/** BASE_USER_PASSWORD_CHANGE_STATUS_NOT_REQUIRED - 当前无需修改密码。 */
|
|
34
|
+
BASE_USER_PASSWORD_CHANGE_STATUS_NOT_REQUIRED = 2,
|
|
35
|
+
}
|
|
@@ -6,10 +6,10 @@ import type {
|
|
|
6
6
|
} from '../../rpc/base/v1/oauth'
|
|
7
7
|
import type { LoginRequest, LoginResponse } from '../../rpc/base/v1/login'
|
|
8
8
|
import type { VerifyMfaRequest } from '../../rpc/base/v1/mfa'
|
|
9
|
-
import { defMfaService } from '../../api/base/mfa'
|
|
10
|
-
import { defAuthService } from '../../api/system/auth'
|
|
11
|
-
import { defLoginService } from '../../api/base/login'
|
|
12
|
-
import { defOauthService } from '../../api/base/oauth'
|
|
9
|
+
import { defMfaService } from '../../api/base/v1/mfa'
|
|
10
|
+
import { defAuthService } from '../../api/system/app/v1/auth'
|
|
11
|
+
import { defLoginService } from '../../api/base/v1/login'
|
|
12
|
+
import { defOauthService } from '../../api/base/v1/oauth'
|
|
13
13
|
import { defineStore } from 'pinia'
|
|
14
14
|
import { ref } from 'vue'
|
|
15
15
|
import {
|
package/src/utils/http.ts
CHANGED
|
@@ -43,10 +43,7 @@ const CAPTCHA_URL = '/v1/base/captcha'
|
|
|
43
43
|
const CONFIG_URL = '/v1/base/config'
|
|
44
44
|
const LANGUAGE_URL = '/v1/base/language'
|
|
45
45
|
const PASSWORD_PUBLIC_KEY_URL = '/v1/base/password-public-key'
|
|
46
|
-
|
|
47
|
-
const LEGACY_REFRESH_TOKEN_URL = `${LEGACY_AUTH_URL}/token`
|
|
48
|
-
const LEGACY_CAPTCHA_URL = '/login/captcha'
|
|
49
|
-
// 认证公共接口不携带旧 token,同时保留旧路径兼容迁移期间的灰度访问。
|
|
46
|
+
// 认证公共接口不携带访问令牌。
|
|
50
47
|
const NO_AUTH_URL_SET = new Set([
|
|
51
48
|
SESSION_URL,
|
|
52
49
|
REFRESH_TOKEN_URL,
|
|
@@ -54,9 +51,6 @@ const NO_AUTH_URL_SET = new Set([
|
|
|
54
51
|
CONFIG_URL,
|
|
55
52
|
LANGUAGE_URL,
|
|
56
53
|
PASSWORD_PUBLIC_KEY_URL,
|
|
57
|
-
LEGACY_AUTH_URL,
|
|
58
|
-
LEGACY_CAPTCHA_URL,
|
|
59
|
-
LEGACY_REFRESH_TOKEN_URL,
|
|
60
54
|
])
|
|
61
55
|
const AUTH_EXPIRED_EXCLUDED_URL_SET = new Set([
|
|
62
56
|
SESSION_URL,
|
|
@@ -64,8 +58,6 @@ const AUTH_EXPIRED_EXCLUDED_URL_SET = new Set([
|
|
|
64
58
|
CONFIG_URL,
|
|
65
59
|
LANGUAGE_URL,
|
|
66
60
|
PASSWORD_PUBLIC_KEY_URL,
|
|
67
|
-
LEGACY_AUTH_URL,
|
|
68
|
-
LEGACY_CAPTCHA_URL,
|
|
69
61
|
])
|
|
70
62
|
|
|
71
63
|
const AUTH_SILENT_LOGOUT_EVENT = 'auth:silent-logout'
|
|
@@ -147,7 +139,7 @@ function isAuthErrorResponse(data: unknown) {
|
|
|
147
139
|
)
|
|
148
140
|
}
|
|
149
141
|
|
|
150
|
-
//
|
|
142
|
+
// 当前请求属于公共认证接口时,不应自动附带登录态。
|
|
151
143
|
function isNoAuthRequest(url: string) {
|
|
152
144
|
return NO_AUTH_URL_SET.has(url)
|
|
153
145
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { defLoginService } from '../api/base/login'
|
|
1
|
+
import { defLoginService } from '../api/base/v1/login'
|
|
2
2
|
import { t } from '../locales'
|
|
3
3
|
import { PasswordCryptoScene } from '../rpc/base/v1/login'
|
|
4
4
|
import type { PasswordCrypto } from '../rpc/common/v1/types'
|
|
5
|
-
|
|
6
|
-
// #ifdef MP-WEIXIN
|
|
7
5
|
import * as miniCrypto from 'asmcrypto.js'
|
|
8
|
-
// #endif
|
|
9
6
|
|
|
10
7
|
type MiniCrypto = typeof import('asmcrypto.js')
|
|
11
8
|
|
|
@@ -53,8 +50,12 @@ function getSubtleCrypto() {
|
|
|
53
50
|
return cryptoApi
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/**
|
|
57
|
-
function
|
|
53
|
+
/** 生成纯 JavaScript 密码加密所需的随机字节。 */
|
|
54
|
+
function getPortableRandomBytes(length: number) {
|
|
55
|
+
const cryptoApi = globalThis.crypto
|
|
56
|
+
if (cryptoApi?.getRandomValues) {
|
|
57
|
+
return Promise.resolve(cryptoApi.getRandomValues(new Uint8Array(length)).buffer)
|
|
58
|
+
}
|
|
58
59
|
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
59
60
|
if (typeof wx === 'undefined' || typeof wx.getRandomValues !== 'function') {
|
|
60
61
|
reject(new Error(t('core.crypto.unsupported')))
|
|
@@ -119,12 +120,16 @@ function trimDerInteger(value: Uint8Array) {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/** 从 PEM SubjectPublicKeyInfo 中提取 RSA 模数和指数。 */
|
|
122
|
-
function
|
|
123
|
+
function parsePortableRsaPublicKey(publicKey: string): [Uint8Array, Uint8Array] {
|
|
123
124
|
const base64 = publicKey
|
|
124
125
|
.replace(/-----BEGIN PUBLIC KEY-----/g, '')
|
|
125
126
|
.replace(/-----END PUBLIC KEY-----/g, '')
|
|
126
127
|
.replace(/\s/g, '')
|
|
127
|
-
const der = new Uint8Array(
|
|
128
|
+
const der = new Uint8Array(
|
|
129
|
+
typeof wx !== 'undefined' && typeof wx.base64ToArrayBuffer === 'function'
|
|
130
|
+
? wx.base64ToArrayBuffer(base64)
|
|
131
|
+
: pemToArrayBuffer(publicKey),
|
|
132
|
+
)
|
|
128
133
|
const subjectPublicKeyInfo = readDerNode(der, 0)
|
|
129
134
|
const algorithm = readDerNode(subjectPublicKeyInfo.value, 0)
|
|
130
135
|
const bitString = readDerNode(subjectPublicKeyInfo.value, algorithm.next)
|
|
@@ -164,13 +169,17 @@ function generateMgf1Mask(crypto: MiniCrypto, seed: Uint8Array, length: number)
|
|
|
164
169
|
return mask
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
/**
|
|
168
|
-
function
|
|
169
|
-
|
|
172
|
+
/** 将字节数组编码为当前运行端可提交的 Base64。 */
|
|
173
|
+
function portableBytesToBase64(value: Uint8Array) {
|
|
174
|
+
const buffer = value.slice().buffer
|
|
175
|
+
if (typeof wx !== 'undefined' && typeof wx.arrayBufferToBase64 === 'function') {
|
|
176
|
+
return wx.arrayBufferToBase64(buffer)
|
|
177
|
+
}
|
|
178
|
+
return arrayBufferToBase64(buffer)
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
/** 使用纯 JavaScript
|
|
173
|
-
async function
|
|
181
|
+
/** 使用纯 JavaScript 实现兼容缺少 WebCrypto 的密码加密协议。 */
|
|
182
|
+
async function encryptPortablePassword(
|
|
174
183
|
crypto: MiniCrypto,
|
|
175
184
|
password: string,
|
|
176
185
|
publicKeyResponse: {
|
|
@@ -180,13 +189,13 @@ async function encryptMiniProgramPassword(
|
|
|
180
189
|
nonce: string
|
|
181
190
|
},
|
|
182
191
|
): Promise<PasswordCrypto> {
|
|
183
|
-
const aesKey = new Uint8Array(await
|
|
184
|
-
const iv = new Uint8Array(await
|
|
192
|
+
const aesKey = new Uint8Array(await getPortableRandomBytes(32))
|
|
193
|
+
const iv = new Uint8Array(await getPortableRandomBytes(12))
|
|
185
194
|
const plaintext = crypto.string_to_bytes(password, true)
|
|
186
|
-
const [modulus, exponent] =
|
|
195
|
+
const [modulus, exponent] = parsePortableRsaPublicKey(publicKeyResponse.public_key)
|
|
187
196
|
const hash = new crypto.Sha256()
|
|
188
197
|
const keySize = Math.ceil(new crypto.BigNumber(modulus).bitLength / 8)
|
|
189
|
-
const seed = new Uint8Array(await
|
|
198
|
+
const seed = new Uint8Array(await getPortableRandomBytes(hash.HASH_SIZE))
|
|
190
199
|
const dataBlockLength = keySize - hash.HASH_SIZE - 1
|
|
191
200
|
const paddingLength = dataBlockLength - aesKey.length - hash.HASH_SIZE - 1
|
|
192
201
|
if (paddingLength < 0) {
|
|
@@ -222,9 +231,9 @@ async function encryptMiniProgramPassword(
|
|
|
222
231
|
key_id: publicKeyResponse.key_id,
|
|
223
232
|
nonce: publicKeyResponse.nonce,
|
|
224
233
|
algorithm: publicKeyResponse.algorithm,
|
|
225
|
-
encrypted_key:
|
|
226
|
-
iv:
|
|
227
|
-
ciphertext:
|
|
234
|
+
encrypted_key: portableBytesToBase64(encryptedKey),
|
|
235
|
+
iv: portableBytesToBase64(iv),
|
|
236
|
+
ciphertext: portableBytesToBase64(ciphertext),
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
|
|
@@ -240,30 +249,31 @@ export async function encryptPassword(
|
|
|
240
249
|
|
|
241
250
|
const publicKeyResponse = await defLoginService.PasswordPublicKey({ scene })
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
const cryptoApi = globalThis.crypto
|
|
253
|
+
const isSecureContext = typeof window === 'undefined' || window.isSecureContext
|
|
254
|
+
if (!cryptoApi?.subtle || !isSecureContext) {
|
|
255
|
+
return encryptPortablePassword(miniCrypto, plainPassword, publicKeyResponse)
|
|
256
|
+
}
|
|
247
257
|
|
|
248
|
-
const
|
|
249
|
-
const publicKey = await
|
|
258
|
+
const subtleCrypto = getSubtleCrypto()
|
|
259
|
+
const publicKey = await subtleCrypto.subtle.importKey(
|
|
250
260
|
'spki',
|
|
251
261
|
pemToArrayBuffer(publicKeyResponse.public_key),
|
|
252
262
|
{ name: 'RSA-OAEP', hash: 'SHA-256' },
|
|
253
263
|
false,
|
|
254
264
|
['encrypt'],
|
|
255
265
|
)
|
|
256
|
-
const aesKey = await
|
|
266
|
+
const aesKey = await subtleCrypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
|
|
257
267
|
'encrypt',
|
|
258
268
|
])
|
|
259
|
-
const rawAesKey = await
|
|
260
|
-
const iv =
|
|
261
|
-
const ciphertext = await
|
|
269
|
+
const rawAesKey = await subtleCrypto.subtle.exportKey('raw', aesKey)
|
|
270
|
+
const iv = subtleCrypto.getRandomValues(new Uint8Array(12))
|
|
271
|
+
const ciphertext = await subtleCrypto.subtle.encrypt(
|
|
262
272
|
{ name: 'AES-GCM', iv },
|
|
263
273
|
aesKey,
|
|
264
274
|
new TextEncoder().encode(plainPassword),
|
|
265
275
|
)
|
|
266
|
-
const encryptedKey = await
|
|
276
|
+
const encryptedKey = await subtleCrypto.subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, rawAesKey)
|
|
267
277
|
|
|
268
278
|
return {
|
|
269
279
|
key_id: publicKeyResponse.key_id,
|
|
@@ -4,8 +4,8 @@ import type { LoginRequest } from '../../../rpc/base/v1/login'
|
|
|
4
4
|
import { LoginStatus, type LoginResponse } from '../../../rpc/base/v1/login'
|
|
5
5
|
import { onLoad } from '@dcloudio/uni-app'
|
|
6
6
|
import { computed, defineAsyncComponent, nextTick, reactive, ref, watch } from 'vue'
|
|
7
|
-
import { defLoginService } from '../../../api/base/login'
|
|
8
|
-
import { defMfaService } from '../../../api/base/mfa'
|
|
7
|
+
import { defLoginService } from '../../../api/base/v1/login'
|
|
8
|
+
import { defMfaService } from '../../../api/base/v1/mfa'
|
|
9
9
|
import defaultLogo from '../../../static/images/logo_icon.png'
|
|
10
10
|
import { homeTabPage } from '../../../utils/navigation'
|
|
11
11
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from '../../../utils/passwordCrypto'
|
|
@@ -1087,12 +1087,17 @@ onLoad(() => {
|
|
|
1087
1087
|
top: calc(24rpx + env(safe-area-inset-top));
|
|
1088
1088
|
right: 28rpx;
|
|
1089
1089
|
z-index: 2;
|
|
1090
|
+
max-width: calc(100% - 56rpx);
|
|
1090
1091
|
}
|
|
1091
1092
|
|
|
1092
1093
|
.login-locale__value {
|
|
1094
|
+
max-width: 240rpx;
|
|
1095
|
+
overflow: hidden;
|
|
1093
1096
|
padding: 12rpx 16rpx;
|
|
1094
1097
|
color: #16806d;
|
|
1095
1098
|
font-size: 24rpx;
|
|
1099
|
+
text-overflow: ellipsis;
|
|
1100
|
+
white-space: nowrap;
|
|
1096
1101
|
}
|
|
1097
1102
|
|
|
1098
1103
|
.login-hero {
|
package/src/vite.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
readFileSync,
|
|
6
6
|
readdirSync,
|
|
7
7
|
rmdirSync,
|
|
8
|
-
rmSync,
|
|
9
8
|
statSync,
|
|
10
9
|
unlinkSync,
|
|
11
10
|
writeFileSync,
|
|
@@ -39,7 +38,6 @@ type BuildTransaction = {
|
|
|
39
38
|
originalPages: string
|
|
40
39
|
generatedFiles: string[]
|
|
41
40
|
generatedStaticFiles: string[]
|
|
42
|
-
generatedStatic?: boolean
|
|
43
41
|
ownerPid?: number
|
|
44
42
|
ready?: boolean
|
|
45
43
|
}
|
|
@@ -368,13 +366,9 @@ function restoreBuildTransaction(transaction: BuildTransaction, transactionFile:
|
|
|
368
366
|
removeEmptyParents(dirname(file), transaction.inputDir)
|
|
369
367
|
}
|
|
370
368
|
removeGeneratedStaticFiles(
|
|
371
|
-
transaction.generatedStaticFiles
|
|
369
|
+
transaction.generatedStaticFiles,
|
|
372
370
|
resolve(transaction.inputDir, 'static'),
|
|
373
371
|
)
|
|
374
|
-
// 兼容旧事务:旧格式仅在 static 原本不存在时记录整个目录由插件生成。
|
|
375
|
-
if (transaction.generatedStatic && transaction.generatedStaticFiles === undefined) {
|
|
376
|
-
rmSync(resolve(transaction.inputDir, 'static'), { recursive: true, force: true })
|
|
377
|
-
}
|
|
378
372
|
writeFileSync(transaction.pagesFile, transaction.originalPages)
|
|
379
373
|
if (existsSync(transactionFile)) unlinkSync(transactionFile)
|
|
380
374
|
}
|
|
@@ -600,10 +594,13 @@ function appendPage(manifest: PagesManifest, page: ScannedPage): void {
|
|
|
600
594
|
function createPageWrapper(page: ScannedPage): string {
|
|
601
595
|
const source = page.source.replace(/\\/g, '/')
|
|
602
596
|
return `<script setup lang="ts">
|
|
597
|
+
import { onShow } from '@dcloudio/uni-app'
|
|
598
|
+
import { setAppPageTitle } from '@liujitcn/kratos-uni-app-core'
|
|
603
599
|
import KratosPage from ${JSON.stringify(source)}
|
|
604
600
|
import KratosTabBar from '@liujitcn/kratos-uni-app-core/components/KratosTabBar.vue'
|
|
605
601
|
|
|
606
602
|
defineOptions({ inheritAttrs: false })
|
|
603
|
+
onShow(() => setAppPageTitle(${JSON.stringify(page.route)}))
|
|
607
604
|
</script>
|
|
608
605
|
|
|
609
606
|
<template>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { http } from '../../utils/http'
|
|
2
|
-
import type { BaseDictForm, BaseDictService } from '../../rpc/system/app/v1/base_dict'
|
|
3
|
-
|
|
4
|
-
const BASE_DICT_URL = '/v1/app/base/dict'
|
|
5
|
-
|
|
6
|
-
/** 字典查询请求兼容结构,支持 code 和旧版 value。 */
|
|
7
|
-
type GetBaseDictRequestCompat = {
|
|
8
|
-
code?: string
|
|
9
|
-
value?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** 字典服务 */
|
|
13
|
-
export class BaseDictServiceImpl implements BaseDictService {
|
|
14
|
-
/** 查询单个字典 */
|
|
15
|
-
GetBaseDict(request: GetBaseDictRequestCompat): Promise<BaseDictForm> {
|
|
16
|
-
const code = request.code ?? request.value ?? ''
|
|
17
|
-
return http<BaseDictForm>({
|
|
18
|
-
url: `${BASE_DICT_URL}/${code}`,
|
|
19
|
-
method: 'GET',
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const defBaseDictService = new BaseDictServiceImpl()
|