@liujitcn/kratos-taro-app-core 0.0.1
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 +36 -0
- package/dist/build.mjs +38 -0
- package/dist/runner.mjs +321 -0
- package/package.json +51 -0
- package/src/api/base/config.ts +20 -0
- package/src/api/base/file.ts +57 -0
- package/src/api/base/login.ts +87 -0
- package/src/api/base/menu.ts +17 -0
- package/src/api/base/oauth.ts +149 -0
- package/src/api/system/auth.ts +54 -0
- package/src/api/system/base_area.ts +34 -0
- package/src/api/system/base_dict.ts +24 -0
- package/src/bootstrap.ts +32 -0
- package/src/build.ts +24 -0
- package/src/components/BootstrapStatus.scss +28 -0
- package/src/components/BootstrapStatus.tsx +25 -0
- package/src/components/KratosPageFrame.scss +41 -0
- package/src/components/KratosPageFrame.tsx +51 -0
- package/src/components/KratosTabBar.scss +34 -0
- package/src/components/KratosTabBar.tsx +45 -0
- package/src/index.ts +36 -0
- package/src/module.ts +63 -0
- package/src/navigation-pattern.d.mts +2 -0
- package/src/navigation-pattern.mjs +29 -0
- package/src/navigation-tree.d.mts +12 -0
- package/src/navigation-tree.mjs +45 -0
- package/src/navigation.ts +336 -0
- package/src/pages.ts +21 -0
- package/src/rpc/base/v1/config.ts +34 -0
- package/src/rpc/base/v1/file.ts +66 -0
- package/src/rpc/base/v1/login.ts +128 -0
- package/src/rpc/base/v1/mcp.ts +20 -0
- package/src/rpc/base/v1/oauth.ts +199 -0
- package/src/rpc/base/v1/sse.ts +22 -0
- package/src/rpc/common/v1/common.ts +71 -0
- package/src/rpc/common/v1/enum.ts +71 -0
- package/src/rpc/common/v1/types.ts +77 -0
- package/src/rpc/google/protobuf/empty.ts +19 -0
- package/src/rpc/google/protobuf/wrappers.ts +124 -0
- package/src/rpc/system/app/v1/auth.ts +54 -0
- package/src/rpc/system/app/v1/base_area.ts +24 -0
- package/src/rpc/system/app/v1/base_dict.ts +37 -0
- package/src/rpc/system/app/v1/base_menu.ts +61 -0
- package/src/rpc/system/common/v1/enum.ts +80 -0
- package/src/runner.ts +390 -0
- package/src/static/images/avatar.png +0 -0
- package/src/static/images/center_bg.png +0 -0
- package/src/static/images/logo_icon.png +0 -0
- package/src/static/images/navigator_bg.png +0 -0
- package/src/static/tabs/home_default.png +0 -0
- package/src/static/tabs/home_selected.png +0 -0
- package/src/static/tabs/user_default.png +0 -0
- package/src/static/tabs/user_selected.png +0 -0
- package/src/stores/index.ts +2 -0
- package/src/stores/setting.ts +43 -0
- package/src/stores/user.ts +152 -0
- package/src/styles/base.scss +45 -0
- package/src/types/assets.d.ts +9 -0
- package/src/utils/auth.ts +55 -0
- package/src/utils/file.ts +48 -0
- package/src/utils/http.ts +262 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/navigation.ts +72 -0
- package/src/utils/passwordCrypto.ts +257 -0
- package/src/views/pages/index/index.scss +137 -0
- package/src/views/pages/index/index.tsx +74 -0
- package/src/views/pages/login/components/BehaviorCaptcha.h5.tsx +46 -0
- package/src/views/pages/login/components/BehaviorCaptcha.tsx +6 -0
- package/src/views/pages/login/components/BehaviorCaptcha.weapp.tsx +6 -0
- package/src/views/pages/login/components/types.ts +32 -0
- package/src/views/pages/login/login.scss +310 -0
- package/src/views/pages/login/login.tsx +493 -0
- package/src/views/pages/login/protocal.scss +84 -0
- package/src/views/pages/login/protocal.tsx +73 -0
- package/src/views/pages/status/index.tsx +42 -0
- package/src/views/pages/webview/webview.scss +43 -0
- package/src/views/pages/webview/webview.tsx +41 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { http } from '../../utils/http'
|
|
2
|
+
import type {
|
|
3
|
+
CreateOauthAuthorizationRequest,
|
|
4
|
+
CreateOauthAuthorizationResponse,
|
|
5
|
+
CreateOauthBindingAuthorizationRequest,
|
|
6
|
+
CreateOauthBindingAuthorizationResponse,
|
|
7
|
+
BindOauthSessionRequest,
|
|
8
|
+
CreateOauthSessionRequest,
|
|
9
|
+
CreateOauthSessionResponse,
|
|
10
|
+
ExchangeOauthTicketRequest,
|
|
11
|
+
ExchangeOauthTicketResponse,
|
|
12
|
+
HandleOauthBindingCallbackRequest,
|
|
13
|
+
HandleOauthBindingCallbackResponse,
|
|
14
|
+
HandleOauthCallbackRequest,
|
|
15
|
+
HandleOauthCallbackResponse,
|
|
16
|
+
ListOauthBindingRequest,
|
|
17
|
+
ListOauthBindingResponse,
|
|
18
|
+
ListOauthProviderRequest,
|
|
19
|
+
ListOauthProviderResponse,
|
|
20
|
+
OauthService,
|
|
21
|
+
UnbindOauthAccountRequest,
|
|
22
|
+
} from '../../rpc/base/v1/oauth'
|
|
23
|
+
import type { Empty } from '../../rpc/google/protobuf/empty'
|
|
24
|
+
|
|
25
|
+
const OAUTH_PROVIDER_URL = '/v1/base/oauth/provider'
|
|
26
|
+
const OAUTH_AUTHORIZATION_URL = '/v1/base/oauth/authorization'
|
|
27
|
+
const OAUTH_CALLBACK_URL = '/v1/base/oauth'
|
|
28
|
+
const OAUTH_TICKET_URL = '/v1/base/oauth/ticket'
|
|
29
|
+
const OAUTH_SESSION_URL = '/v1/base/oauth/session'
|
|
30
|
+
const OAUTH_BINDING_URL = '/v1/base/oauth/binding'
|
|
31
|
+
const OAUTH_BINDING_AUTHORIZATION_URL = '/v1/base/oauth/binding/authorization'
|
|
32
|
+
|
|
33
|
+
/** 三方登录公共服务 */
|
|
34
|
+
export class OauthServiceImpl implements OauthService {
|
|
35
|
+
/** 查询三方登录方式 */
|
|
36
|
+
ListOauthProvider(request: ListOauthProviderRequest): Promise<ListOauthProviderResponse> {
|
|
37
|
+
return http<ListOauthProviderResponse>({
|
|
38
|
+
url: `${OAUTH_PROVIDER_URL}`,
|
|
39
|
+
method: 'GET',
|
|
40
|
+
authMode: 'none',
|
|
41
|
+
data: request,
|
|
42
|
+
header: { Authorization: 'no-auth' },
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 创建三方登录授权地址 */
|
|
47
|
+
CreateOauthAuthorization(
|
|
48
|
+
request: CreateOauthAuthorizationRequest,
|
|
49
|
+
): Promise<CreateOauthAuthorizationResponse> {
|
|
50
|
+
return http<CreateOauthAuthorizationResponse>({
|
|
51
|
+
url: `${OAUTH_AUTHORIZATION_URL}`,
|
|
52
|
+
method: 'POST',
|
|
53
|
+
authMode: 'none',
|
|
54
|
+
data: request,
|
|
55
|
+
header: { Authorization: 'no-auth' },
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 处理三方登录回调 */
|
|
60
|
+
HandleOauthCallback(request: HandleOauthCallbackRequest): Promise<HandleOauthCallbackResponse> {
|
|
61
|
+
return http<HandleOauthCallbackResponse>({
|
|
62
|
+
url: `${OAUTH_CALLBACK_URL}/${request.provider}/callback`,
|
|
63
|
+
method: 'GET',
|
|
64
|
+
authMode: 'none',
|
|
65
|
+
data: request,
|
|
66
|
+
header: { Authorization: 'no-auth' },
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 兑换三方登录票据 */
|
|
71
|
+
ExchangeOauthTicket(request: ExchangeOauthTicketRequest): Promise<ExchangeOauthTicketResponse> {
|
|
72
|
+
return http<ExchangeOauthTicketResponse>({
|
|
73
|
+
url: `${OAUTH_TICKET_URL}`,
|
|
74
|
+
method: 'POST',
|
|
75
|
+
authMode: 'none',
|
|
76
|
+
data: request,
|
|
77
|
+
header: { Authorization: 'no-auth' },
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 创建三方登录会话 */
|
|
82
|
+
CreateOauthSession(request: CreateOauthSessionRequest): Promise<CreateOauthSessionResponse> {
|
|
83
|
+
return http<CreateOauthSessionResponse>({
|
|
84
|
+
url: `${OAUTH_SESSION_URL}`,
|
|
85
|
+
method: 'POST',
|
|
86
|
+
authMode: 'none',
|
|
87
|
+
data: request,
|
|
88
|
+
header: { Authorization: 'no-auth' },
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 绑定已有账号并创建三方登录会话 */
|
|
93
|
+
BindOauthSession(request: BindOauthSessionRequest): Promise<CreateOauthSessionResponse> {
|
|
94
|
+
return http<CreateOauthSessionResponse>({
|
|
95
|
+
url: `${OAUTH_BINDING_URL}/session`,
|
|
96
|
+
method: 'POST',
|
|
97
|
+
authMode: 'none',
|
|
98
|
+
data: request,
|
|
99
|
+
header: { Authorization: 'no-auth' },
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 查询个人中心三方账号绑定列表 */
|
|
104
|
+
ListOauthBinding(request: ListOauthBindingRequest): Promise<ListOauthBindingResponse> {
|
|
105
|
+
return http<ListOauthBindingResponse>({
|
|
106
|
+
url: `${OAUTH_BINDING_URL}`,
|
|
107
|
+
method: 'GET',
|
|
108
|
+
authMode: 'required',
|
|
109
|
+
data: request,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 创建个人中心三方账号绑定授权地址 */
|
|
114
|
+
CreateOauthBindingAuthorization(
|
|
115
|
+
request: CreateOauthBindingAuthorizationRequest,
|
|
116
|
+
): Promise<CreateOauthBindingAuthorizationResponse> {
|
|
117
|
+
return http<CreateOauthBindingAuthorizationResponse>({
|
|
118
|
+
url: `${OAUTH_BINDING_AUTHORIZATION_URL}`,
|
|
119
|
+
method: 'POST',
|
|
120
|
+
authMode: 'required',
|
|
121
|
+
data: request,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 处理个人中心三方账号绑定回调 */
|
|
126
|
+
HandleOauthBindingCallback(
|
|
127
|
+
request: HandleOauthBindingCallbackRequest,
|
|
128
|
+
): Promise<HandleOauthBindingCallbackResponse> {
|
|
129
|
+
return http<HandleOauthBindingCallbackResponse>({
|
|
130
|
+
url: `${OAUTH_CALLBACK_URL}/${request.provider}/binding/callback`,
|
|
131
|
+
method: 'GET',
|
|
132
|
+
authMode: 'none',
|
|
133
|
+
data: request,
|
|
134
|
+
header: { Authorization: 'no-auth' },
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 解绑个人中心三方账号 */
|
|
139
|
+
UnbindOauthAccount(request: UnbindOauthAccountRequest): Promise<Empty> {
|
|
140
|
+
return http<Empty>({
|
|
141
|
+
url: `${OAUTH_BINDING_URL}/${request.provider}`,
|
|
142
|
+
method: 'DELETE',
|
|
143
|
+
authMode: 'required',
|
|
144
|
+
data: request,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const defOauthService = new OauthServiceImpl()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { http } from '../../utils/http'
|
|
2
|
+
import {
|
|
3
|
+
type AuthService,
|
|
4
|
+
type BindUserPhoneRequest,
|
|
5
|
+
type BindUserPhoneResponse,
|
|
6
|
+
type UserProfileForm,
|
|
7
|
+
} from '../../rpc/system/app/v1/auth'
|
|
8
|
+
import type { Empty } from '../../rpc/google/protobuf/empty'
|
|
9
|
+
|
|
10
|
+
const AUTH_URL = '/v1/app/auth'
|
|
11
|
+
|
|
12
|
+
/** 获取用户资料请求兼容空请求结构。 */
|
|
13
|
+
type GetUserProfileRequestCompat = Empty
|
|
14
|
+
|
|
15
|
+
/** 更新用户资料请求兼容新旧两种表单包裹方式。 */
|
|
16
|
+
type UpdateUserProfileRequestCompat = Partial<UserProfileForm> & {
|
|
17
|
+
user_profile?: UserProfileForm
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 用户认证资料服务 */
|
|
21
|
+
export class AuthServiceImpl implements AuthService {
|
|
22
|
+
/** 获取已登录用户资料 */
|
|
23
|
+
GetUserProfile(request: GetUserProfileRequestCompat): Promise<UserProfileForm> {
|
|
24
|
+
return http<UserProfileForm>({
|
|
25
|
+
url: `${AUTH_URL}/profile`,
|
|
26
|
+
method: 'GET',
|
|
27
|
+
authMode: 'required',
|
|
28
|
+
data: request,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 修改个人中心用户信息 */
|
|
33
|
+
UpdateUserProfile(request: UpdateUserProfileRequestCompat): Promise<Empty> {
|
|
34
|
+
const userProfile = request.user_profile ?? (request as UserProfileForm)
|
|
35
|
+
return http<Empty>({
|
|
36
|
+
url: `${AUTH_URL}/profile`,
|
|
37
|
+
method: 'PUT',
|
|
38
|
+
authMode: 'required',
|
|
39
|
+
data: userProfile,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 手机号授权 */
|
|
44
|
+
BindUserPhone(request: BindUserPhoneRequest): Promise<BindUserPhoneResponse> {
|
|
45
|
+
return http<BindUserPhoneResponse>({
|
|
46
|
+
url: `${AUTH_URL}/phone`,
|
|
47
|
+
method: 'PUT',
|
|
48
|
+
authMode: 'required',
|
|
49
|
+
data: request,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const defAuthService = new AuthServiceImpl()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { http } from '../../utils/http'
|
|
2
|
+
import type {
|
|
3
|
+
BaseAreaService,
|
|
4
|
+
TreeBaseAreaRequest,
|
|
5
|
+
TreeBaseAreaResponse,
|
|
6
|
+
} from '../../rpc/system/app/v1/base_area'
|
|
7
|
+
|
|
8
|
+
const BASE_AREA_URL = '/v1/app/base/area'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 行政区域服务实现。
|
|
12
|
+
*/
|
|
13
|
+
export class BaseAreaServiceImpl implements BaseAreaService {
|
|
14
|
+
/**
|
|
15
|
+
* 查询行政区域树形列表。
|
|
16
|
+
*/
|
|
17
|
+
async TreeBaseArea(request: TreeBaseAreaRequest): Promise<TreeBaseAreaResponse> {
|
|
18
|
+
const response = await http<Partial<TreeBaseAreaResponse>>({
|
|
19
|
+
url: `${BASE_AREA_URL}/tree`,
|
|
20
|
+
method: 'GET',
|
|
21
|
+
data: request,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...response,
|
|
26
|
+
areas: response.areas ?? [],
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 默认行政区域服务。
|
|
33
|
+
*/
|
|
34
|
+
export const defBaseAreaService = new BaseAreaServiceImpl()
|
|
@@ -0,0 +1,24 @@
|
|
|
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()
|
package/src/bootstrap.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
registerKratosTaroModules,
|
|
3
|
+
type KratosTaroModule,
|
|
4
|
+
} from './module'
|
|
5
|
+
import { initializeAppNavigation } from './navigation'
|
|
6
|
+
import {
|
|
7
|
+
registerUserStoreExtension,
|
|
8
|
+
startUserStoreEventBridge,
|
|
9
|
+
useUserStore,
|
|
10
|
+
} from './stores'
|
|
11
|
+
|
|
12
|
+
/** Taro 应用启动参数。 */
|
|
13
|
+
export interface KratosTaroBootstrapOptions {
|
|
14
|
+
/** 按覆盖优先级排列的模块。 */
|
|
15
|
+
modules: KratosTaroModule[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let bootstrapped = false
|
|
19
|
+
|
|
20
|
+
/** 注册模块、认证生命周期和导航运行时。 */
|
|
21
|
+
export function bootstrapKratosTaroApp(options: KratosTaroBootstrapOptions): void {
|
|
22
|
+
registerKratosTaroModules(options.modules)
|
|
23
|
+
if (bootstrapped) return
|
|
24
|
+
bootstrapped = true
|
|
25
|
+
startUserStoreEventBridge()
|
|
26
|
+
registerUserStoreExtension({
|
|
27
|
+
onLogin: initializeAppNavigation,
|
|
28
|
+
onLogout: initializeAppNavigation,
|
|
29
|
+
onSilentLogout: initializeAppNavigation,
|
|
30
|
+
})
|
|
31
|
+
useUserStore.getState().hydrate()
|
|
32
|
+
}
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { corePages } from './pages'
|
|
4
|
+
|
|
5
|
+
/** 构建期模块描述。 */
|
|
6
|
+
export interface KratosTaroBuildModule {
|
|
7
|
+
name: string
|
|
8
|
+
root: string
|
|
9
|
+
pages: Record<string, { route?: string; style?: Record<string, unknown> }>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 声明构建期模块描述。 */
|
|
13
|
+
export function defineKratosTaroBuildModule<T extends KratosTaroBuildModule>(module: T): T {
|
|
14
|
+
return module
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
18
|
+
|
|
19
|
+
/** core 构建期模块描述。 */
|
|
20
|
+
export const buildModule = defineKratosTaroBuildModule({
|
|
21
|
+
name: '@liujitcn/kratos-taro-app-core',
|
|
22
|
+
root: packageRoot,
|
|
23
|
+
pages: corePages,
|
|
24
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.bootstrap-status {
|
|
2
|
+
display: flex;
|
|
3
|
+
min-height: 100vh;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
padding: 48px;
|
|
8
|
+
background: #f8f8f8;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.bootstrap-status__logo {
|
|
13
|
+
width: 112px;
|
|
14
|
+
height: 112px;
|
|
15
|
+
margin-bottom: 32px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.bootstrap-status__title {
|
|
19
|
+
color: #222;
|
|
20
|
+
font-size: 32px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.bootstrap-status__detail {
|
|
24
|
+
margin-top: 16px;
|
|
25
|
+
color: #777;
|
|
26
|
+
font-size: 24px;
|
|
27
|
+
text-align: center;
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Image, Text, View } from '@tarojs/components'
|
|
2
|
+
import './BootstrapStatus.scss'
|
|
3
|
+
|
|
4
|
+
/** 启动状态组件参数。 */
|
|
5
|
+
export interface BootstrapStatusProps {
|
|
6
|
+
title?: string
|
|
7
|
+
detail?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** 全屏启动状态。 */
|
|
11
|
+
export function BootstrapStatus({ title, detail }: BootstrapStatusProps) {
|
|
12
|
+
return (
|
|
13
|
+
<View className='bootstrap-status'>
|
|
14
|
+
<Image
|
|
15
|
+
className='bootstrap-status__logo'
|
|
16
|
+
src='/static/images/logo_icon.png'
|
|
17
|
+
mode='aspectFit'
|
|
18
|
+
/>
|
|
19
|
+
<Text className='bootstrap-status__title'>{title || '正在加载'}</Text>
|
|
20
|
+
{detail ? <Text className='bootstrap-status__detail'>{detail}</Text> : null}
|
|
21
|
+
</View>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default BootstrapStatus
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.kratos-page-frame__navigation {
|
|
2
|
+
position: relative;
|
|
3
|
+
z-index: 1000;
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-shrink: 0;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
width: 100%;
|
|
9
|
+
height: 44PX;
|
|
10
|
+
border-bottom: 1PX solid #eee;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.kratos-page-frame__back {
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: 0;
|
|
17
|
+
left: 0;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
width: 44PX;
|
|
22
|
+
height: 44PX;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.kratos-page-frame__back-icon {
|
|
26
|
+
width: 10PX;
|
|
27
|
+
height: 10PX;
|
|
28
|
+
border-bottom: 2PX solid currentColor;
|
|
29
|
+
border-left: 2PX solid currentColor;
|
|
30
|
+
transform: rotate(45deg);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.kratos-page-frame__title {
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
max-width: calc(100% - 88PX);
|
|
36
|
+
font-size: 16PX;
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
line-height: 44PX;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Text, View } from '@tarojs/components'
|
|
2
|
+
import Taro from '@tarojs/taro'
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import { homeTabPage } from '../utils/navigation'
|
|
5
|
+
import './KratosPageFrame.scss'
|
|
6
|
+
|
|
7
|
+
/** KratosPageFrameProps 页面框架属性。 */
|
|
8
|
+
export interface KratosPageFrameProps {
|
|
9
|
+
children: ReactNode
|
|
10
|
+
navigationStyle: string
|
|
11
|
+
navigationBarTitleText: string
|
|
12
|
+
navigationBarBackgroundColor: string
|
|
13
|
+
navigationBarTextStyle: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** KratosPageFrame 补齐 Taro H5 不渲染原生导航栏的差异。 */
|
|
17
|
+
export function KratosPageFrame({
|
|
18
|
+
children,
|
|
19
|
+
navigationStyle,
|
|
20
|
+
navigationBarTitleText,
|
|
21
|
+
navigationBarBackgroundColor,
|
|
22
|
+
navigationBarTextStyle,
|
|
23
|
+
}: KratosPageFrameProps) {
|
|
24
|
+
if (process.env.TARO_ENV !== 'h5' || navigationStyle === 'custom') return children
|
|
25
|
+
|
|
26
|
+
const color = navigationBarTextStyle === 'white' ? '#fff' : '#000'
|
|
27
|
+
const goBack = async () => {
|
|
28
|
+
try {
|
|
29
|
+
await Taro.navigateBack({ delta: 1 })
|
|
30
|
+
} catch {
|
|
31
|
+
await Taro.reLaunch({ url: homeTabPage })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<View
|
|
38
|
+
className='kratos-page-frame__navigation'
|
|
39
|
+
style={{ backgroundColor: navigationBarBackgroundColor, color }}
|
|
40
|
+
>
|
|
41
|
+
<View className='kratos-page-frame__back' aria-label='返回' onClick={() => void goBack()}>
|
|
42
|
+
<View className='kratos-page-frame__back-icon' />
|
|
43
|
+
</View>
|
|
44
|
+
<Text className='kratos-page-frame__title'>{navigationBarTitleText}</Text>
|
|
45
|
+
</View>
|
|
46
|
+
{children}
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default KratosPageFrame
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.kratos-tab-bar {
|
|
2
|
+
position: fixed;
|
|
3
|
+
right: 0;
|
|
4
|
+
bottom: 0;
|
|
5
|
+
left: 0;
|
|
6
|
+
z-index: 999;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
display: flex;
|
|
9
|
+
height: calc(48PX + env(safe-area-inset-bottom));
|
|
10
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
11
|
+
border-top: 1PX solid #eee;
|
|
12
|
+
background: #fff;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.kratos-tab-bar__item {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex: 1;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
color: #333;
|
|
22
|
+
font-size: 10PX;
|
|
23
|
+
line-height: 12PX;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.kratos-tab-bar__icon {
|
|
27
|
+
width: 28PX;
|
|
28
|
+
height: 28PX;
|
|
29
|
+
margin-bottom: 4PX;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.kratos-tab-bar__text--active {
|
|
33
|
+
color: #27ba9b;
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Image, Text, View } from '@tarojs/components'
|
|
2
|
+
import { resolveModuleIcon, resolveStaticView } from '../module'
|
|
3
|
+
import { APP_MENU_ROOT_ID, navigateAppRoute, useAppNavigation } from '../navigation'
|
|
4
|
+
import { resolveRootMenuId } from '../navigation-tree.mjs'
|
|
5
|
+
import './KratosTabBar.scss'
|
|
6
|
+
|
|
7
|
+
/** 自绘 TabBar 参数。 */
|
|
8
|
+
export interface KratosTabBarProps {
|
|
9
|
+
route: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 根据动态菜单显示的自绘 TabBar。 */
|
|
13
|
+
export function KratosTabBar({ route }: KratosTabBarProps) {
|
|
14
|
+
const menus = useAppNavigation((state) => state.menus)
|
|
15
|
+
const tabBar = useAppNavigation((state) => state.tabBar)
|
|
16
|
+
const routeMenu = menus.find((menu) => resolveStaticView(menu.viewKey) === route)
|
|
17
|
+
const tabMenuId = routeMenu
|
|
18
|
+
? resolveRootMenuId(menus, routeMenu.id, APP_MENU_ROOT_ID)
|
|
19
|
+
: undefined
|
|
20
|
+
const activeMenu = tabBar.find((menu) => menu.id === tabMenuId)
|
|
21
|
+
if (!activeMenu || !tabBar.length) return null
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View className='kratos-tab-bar'>
|
|
25
|
+
{tabBar.map((item) => {
|
|
26
|
+
const active = activeMenu.id === item.id
|
|
27
|
+
const icon = resolveModuleIcon(active && item.selectedIcon ? item.selectedIcon : item.icon)
|
|
28
|
+
return (
|
|
29
|
+
<View
|
|
30
|
+
key={item.id}
|
|
31
|
+
className='kratos-tab-bar__item'
|
|
32
|
+
onClick={() => navigateAppRoute(item.path)}
|
|
33
|
+
>
|
|
34
|
+
{icon ? (
|
|
35
|
+
<Image className='kratos-tab-bar__icon' src={icon} mode='aspectFit' />
|
|
36
|
+
) : null}
|
|
37
|
+
<Text className={active ? 'kratos-tab-bar__text--active' : ''}>{item.title}</Text>
|
|
38
|
+
</View>
|
|
39
|
+
)
|
|
40
|
+
})}
|
|
41
|
+
</View>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default KratosTabBar
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { corePages } from './pages'
|
|
2
|
+
import { defineKratosTaroModule } from './module'
|
|
3
|
+
|
|
4
|
+
/** core 内置模块。 */
|
|
5
|
+
export const coreModule = defineKratosTaroModule({
|
|
6
|
+
name: '@liujitcn/kratos-taro-app-core',
|
|
7
|
+
pages: corePages,
|
|
8
|
+
views: {
|
|
9
|
+
HOME: 'pages/index/index',
|
|
10
|
+
LOGIN: 'pages/login/login',
|
|
11
|
+
PROTOCOL: 'pages/login/protocal',
|
|
12
|
+
WEBVIEW: 'pages/webview/webview',
|
|
13
|
+
BOOTSTRAP_LOADING: 'pages/status/index',
|
|
14
|
+
FORBIDDEN: 'pages/status/index',
|
|
15
|
+
NOT_FOUND: 'pages/status/index',
|
|
16
|
+
OFFLINE: 'pages/status/index',
|
|
17
|
+
CONFIG_ERROR: 'pages/status/index',
|
|
18
|
+
PAGE_UNAVAILABLE: 'pages/status/index',
|
|
19
|
+
},
|
|
20
|
+
icons: {
|
|
21
|
+
HOME_DEFAULT: 'static/tabs/home_default.png',
|
|
22
|
+
HOME_SELECTED: 'static/tabs/home_selected.png',
|
|
23
|
+
USER_DEFAULT: 'static/tabs/user_default.png',
|
|
24
|
+
USER_SELECTED: 'static/tabs/user_selected.png',
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export * from './module'
|
|
29
|
+
export * from './bootstrap'
|
|
30
|
+
export * from './navigation'
|
|
31
|
+
export * from './stores'
|
|
32
|
+
export * from './utils/auth'
|
|
33
|
+
export * from './utils/file'
|
|
34
|
+
export * from './utils/index'
|
|
35
|
+
export * from './utils/navigation'
|
|
36
|
+
export * from './utils/passwordCrypto'
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** bootstrap 可见状态键。 */
|
|
2
|
+
export type BootstrapViewKey =
|
|
3
|
+
| 'BOOTSTRAP_LOADING'
|
|
4
|
+
| 'FORBIDDEN'
|
|
5
|
+
| 'NOT_FOUND'
|
|
6
|
+
| 'OFFLINE'
|
|
7
|
+
| 'CONFIG_ERROR'
|
|
8
|
+
| 'PAGE_UNAVAILABLE'
|
|
9
|
+
|
|
10
|
+
/** 页面编译配置。 */
|
|
11
|
+
export interface KratosTaroPageConfig {
|
|
12
|
+
route?: string
|
|
13
|
+
style?: Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Taro 模块定义。 */
|
|
17
|
+
export interface KratosTaroModule {
|
|
18
|
+
name: string
|
|
19
|
+
pages: Record<string, KratosTaroPageConfig>
|
|
20
|
+
views: Record<string, string>
|
|
21
|
+
icons?: Record<string, string>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 声明 Taro 模块。 */
|
|
25
|
+
export function defineKratosTaroModule<T extends KratosTaroModule>(module: T): T {
|
|
26
|
+
return module
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let registeredModules: KratosTaroModule[] = []
|
|
30
|
+
|
|
31
|
+
/** 原子替换宿主当前注册的模块。 */
|
|
32
|
+
export function registerKratosTaroModules(modules: KratosTaroModule[]): void {
|
|
33
|
+
registeredModules = [...modules]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 获取当前模块列表。 */
|
|
37
|
+
export function getRegisteredKratosTaroModules(): KratosTaroModule[] {
|
|
38
|
+
return [...registeredModules]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 按后注册优先级解析静态视图。 */
|
|
42
|
+
export function resolveStaticView(viewKey: string): string | undefined {
|
|
43
|
+
for (let index = registeredModules.length - 1; index >= 0; index -= 1) {
|
|
44
|
+
const route = registeredModules[index].views[viewKey]
|
|
45
|
+
if (route) return route
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 按后注册优先级解析模块图标。 */
|
|
50
|
+
export function resolveModuleIcon(icon: string | undefined): string | undefined {
|
|
51
|
+
if (!icon || /^https:\/\//.test(icon)) return icon
|
|
52
|
+
for (let index = registeredModules.length - 1; index >= 0; index -= 1) {
|
|
53
|
+
const source = registeredModules[index].icons?.[icon]
|
|
54
|
+
if (source) return resolveBundledAsset(source)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 解析随应用打包的静态资源路径。 */
|
|
59
|
+
export function resolveBundledAsset(source: string): string {
|
|
60
|
+
if (/^(?:https?:|data:)/.test(source)) return source
|
|
61
|
+
const base = process.env.KRATOS_TARO_PUBLIC_PATH || '/'
|
|
62
|
+
return `${base.replace(/\/?$/, '/')}${source.replace(/^\/+/, '')}`
|
|
63
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** 匹配逻辑路由,并提取冒号参数。 */
|
|
2
|
+
export function matchLogicalPath(pattern, path) {
|
|
3
|
+
const patternParts = pattern.replace(/^\/+|\/+$/g, '').split('/')
|
|
4
|
+
const pathParts = path.replace(/^\/+|\/+$/g, '').split('/')
|
|
5
|
+
if (patternParts.length !== pathParts.length) return
|
|
6
|
+
const params = {}
|
|
7
|
+
for (let index = 0; index < patternParts.length; index += 1) {
|
|
8
|
+
const patternPart = patternParts[index]
|
|
9
|
+
const pathPart = pathParts[index]
|
|
10
|
+
if (patternPart.startsWith(':')) {
|
|
11
|
+
params[patternPart.slice(1)] = decodeURIComponent(pathPart)
|
|
12
|
+
} else if (patternPart !== pathPart) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return params
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 解析逻辑路由 query。 */
|
|
20
|
+
export function parseLogicalQuery(query) {
|
|
21
|
+
return query
|
|
22
|
+
.split('&')
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.reduce((result, item) => {
|
|
25
|
+
const [key, value = ''] = item.split('=', 2)
|
|
26
|
+
result[decodeURIComponent(key)] = decodeURIComponent(value)
|
|
27
|
+
return result
|
|
28
|
+
}, {})
|
|
29
|
+
}
|