@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,12 @@
|
|
|
1
|
+
export type MenuNode<T> = T & { children: Array<MenuNode<T>> }
|
|
2
|
+
|
|
3
|
+
export function buildMenuTree<T extends { id: number; parentId?: number }>(
|
|
4
|
+
menus: ReadonlyArray<T>,
|
|
5
|
+
rootId: number,
|
|
6
|
+
): Array<MenuNode<T>>
|
|
7
|
+
|
|
8
|
+
export function resolveRootMenuId(
|
|
9
|
+
menus: ReadonlyArray<{ id: number; parentId?: number }>,
|
|
10
|
+
menuId: number,
|
|
11
|
+
rootId: number,
|
|
12
|
+
): number | undefined
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template {{id: number, parentId?: number}} T
|
|
3
|
+
* @typedef {T & {children: MenuNode<T>[]}} MenuNode
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 按父级编号将移动端扁平菜单构建为根目录下的树。
|
|
8
|
+
* @template {{id: number, parentId?: number}} T
|
|
9
|
+
* @param {ReadonlyArray<T>} menus
|
|
10
|
+
* @param {number} rootId
|
|
11
|
+
* @returns {MenuNode<T>[]}
|
|
12
|
+
*/
|
|
13
|
+
export function buildMenuTree(menus, rootId) {
|
|
14
|
+
/** @type {Map<number, MenuNode<T>>} */
|
|
15
|
+
const nodeMap = new Map(menus.map((menu) => [menu.id, { ...menu, children: [] }]))
|
|
16
|
+
/** @type {MenuNode<T>[]} */
|
|
17
|
+
const roots = []
|
|
18
|
+
for (const node of nodeMap.values()) {
|
|
19
|
+
if (node.parentId === rootId) {
|
|
20
|
+
roots.push(node)
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
nodeMap.get(node.parentId)?.children.push(node)
|
|
24
|
+
}
|
|
25
|
+
return roots
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 沿父级关系查找页面所属的移动端二级菜单编号。
|
|
30
|
+
* @param {ReadonlyArray<{id: number, parentId?: number}>} menus
|
|
31
|
+
* @param {number} menuId
|
|
32
|
+
* @param {number} rootId
|
|
33
|
+
*/
|
|
34
|
+
export function resolveRootMenuId(menus, menuId, rootId) {
|
|
35
|
+
const menuMap = new Map(menus.map((menu) => [menu.id, menu]))
|
|
36
|
+
const visited = new Set()
|
|
37
|
+
let currentId = menuId
|
|
38
|
+
while (!visited.has(currentId)) {
|
|
39
|
+
visited.add(currentId)
|
|
40
|
+
const current = menuMap.get(currentId)
|
|
41
|
+
if (!current) return
|
|
42
|
+
if (current.parentId === rootId) return current.id
|
|
43
|
+
currentId = current.parentId
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import Taro from '@tarojs/taro'
|
|
2
|
+
import { create } from 'zustand'
|
|
3
|
+
import { defBaseMenuService } from './api/base/menu'
|
|
4
|
+
import { resolveStaticView } from './module'
|
|
5
|
+
import { matchLogicalPath, parseLogicalQuery } from './navigation-pattern.mjs'
|
|
6
|
+
import { buildMenuTree } from './navigation-tree.mjs'
|
|
7
|
+
import { hasValidToken } from './utils/auth'
|
|
8
|
+
import { navigateToLogin } from './utils/navigation'
|
|
9
|
+
|
|
10
|
+
/** 移动菜单访问模式。 */
|
|
11
|
+
export type AppMenuAccess = 'PUBLIC' | 'GUEST_ONLY' | 'AUTHENTICATED'
|
|
12
|
+
|
|
13
|
+
/** Taro 运行时使用的扁平菜单。 */
|
|
14
|
+
export interface AppMenu {
|
|
15
|
+
id: number
|
|
16
|
+
parentId?: number
|
|
17
|
+
name: string
|
|
18
|
+
path: string
|
|
19
|
+
viewKey: string
|
|
20
|
+
title: string
|
|
21
|
+
access: AppMenuAccess
|
|
22
|
+
inTabBar: boolean
|
|
23
|
+
icon?: string
|
|
24
|
+
selectedIcon?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Taro 运行时使用的树形菜单节点。 */
|
|
28
|
+
export interface AppMenuNode extends AppMenu {
|
|
29
|
+
children: AppMenuNode[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 菜单获取适配器。 */
|
|
33
|
+
export interface AppNavigationAdapter {
|
|
34
|
+
/** 获取扁平移动菜单配置。 */
|
|
35
|
+
list(): Promise<unknown>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 逻辑路由解析结果。 */
|
|
39
|
+
export interface ResolvedAppRoute {
|
|
40
|
+
menu: AppMenu
|
|
41
|
+
physicalRoute: string
|
|
42
|
+
query: Record<string, string>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 导航响应式状态。 */
|
|
46
|
+
export interface AppNavigationState {
|
|
47
|
+
menus: AppMenu[]
|
|
48
|
+
ready: boolean
|
|
49
|
+
menuTree: AppMenuNode[]
|
|
50
|
+
tabBar: AppMenuNode[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ANONYMOUS_CACHE_KEY = 'kratos-taro-app:navigation:anonymous'
|
|
54
|
+
const AUTHENTICATED_CACHE_KEY = 'kratos-taro-app:navigation:authenticated'
|
|
55
|
+
/** 固定移动端菜单根目录编号。 */
|
|
56
|
+
export const APP_MENU_ROOT_ID = 999
|
|
57
|
+
/** 本地兜底移动菜单。 */
|
|
58
|
+
export const defaultAppMenus: AppMenu[] = [
|
|
59
|
+
{
|
|
60
|
+
id: 99901,
|
|
61
|
+
parentId: APP_MENU_ROOT_ID,
|
|
62
|
+
name: 'AppHome',
|
|
63
|
+
path: 'app/home',
|
|
64
|
+
viewKey: 'HOME',
|
|
65
|
+
title: '首页',
|
|
66
|
+
access: 'PUBLIC',
|
|
67
|
+
inTabBar: true,
|
|
68
|
+
icon: 'HOME_DEFAULT',
|
|
69
|
+
selectedIcon: 'HOME_SELECTED',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 99909,
|
|
73
|
+
parentId: APP_MENU_ROOT_ID,
|
|
74
|
+
name: 'AppMy',
|
|
75
|
+
path: 'app/my',
|
|
76
|
+
viewKey: 'PROFILE_HOME',
|
|
77
|
+
title: '我的',
|
|
78
|
+
access: 'PUBLIC',
|
|
79
|
+
inTabBar: true,
|
|
80
|
+
icon: 'USER_DEFAULT',
|
|
81
|
+
selectedIcon: 'USER_SELECTED',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 9990101,
|
|
85
|
+
parentId: 99901,
|
|
86
|
+
name: 'AppLogin',
|
|
87
|
+
path: 'app/login',
|
|
88
|
+
viewKey: 'LOGIN',
|
|
89
|
+
title: '登录',
|
|
90
|
+
access: 'GUEST_ONLY',
|
|
91
|
+
inTabBar: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 999010101,
|
|
95
|
+
parentId: 9990101,
|
|
96
|
+
name: 'AppProtocol',
|
|
97
|
+
path: 'app/protocol/:type',
|
|
98
|
+
viewKey: 'PROTOCOL',
|
|
99
|
+
title: '协议详情',
|
|
100
|
+
access: 'PUBLIC',
|
|
101
|
+
inTabBar: false,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 9990901,
|
|
105
|
+
parentId: 99909,
|
|
106
|
+
name: 'AppProfile',
|
|
107
|
+
path: 'app/profile',
|
|
108
|
+
viewKey: 'PROFILE',
|
|
109
|
+
title: '个人信息',
|
|
110
|
+
access: 'AUTHENTICATED',
|
|
111
|
+
inTabBar: false,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 9990902,
|
|
115
|
+
parentId: 99909,
|
|
116
|
+
name: 'AppSettings',
|
|
117
|
+
path: 'app/settings',
|
|
118
|
+
viewKey: 'SETTINGS',
|
|
119
|
+
title: '设置',
|
|
120
|
+
access: 'AUTHENTICATED',
|
|
121
|
+
inTabBar: false,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 9990903,
|
|
125
|
+
parentId: 99909,
|
|
126
|
+
name: 'AppAi',
|
|
127
|
+
path: 'app/ai',
|
|
128
|
+
viewKey: 'AI',
|
|
129
|
+
title: 'AI 助手',
|
|
130
|
+
access: 'AUTHENTICATED',
|
|
131
|
+
inTabBar: false,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 9990102,
|
|
135
|
+
parentId: 99901,
|
|
136
|
+
name: 'AppWebView',
|
|
137
|
+
path: 'app/webview',
|
|
138
|
+
viewKey: 'WEBVIEW',
|
|
139
|
+
title: '',
|
|
140
|
+
access: 'PUBLIC',
|
|
141
|
+
inTabBar: false,
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
function createNavigationState(menus: AppMenu[], ready: boolean): AppNavigationState {
|
|
146
|
+
const menuTree = buildMenuTree(menus, APP_MENU_ROOT_ID) as AppMenuNode[]
|
|
147
|
+
return { menus, ready, menuTree, tabBar: menuTree.filter((menu) => menu.inTabBar) }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** 应用导航 Zustand Store。 */
|
|
151
|
+
export const useAppNavigation = create<AppNavigationState>(() =>
|
|
152
|
+
createNavigationState(defaultAppMenus, false),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
let adapter: AppNavigationAdapter = {
|
|
156
|
+
list: () => defBaseMenuService.ListBaseMenu(),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 替换导航远端适配器,供宿主接入自定义契约。 */
|
|
160
|
+
export function setAppNavigationAdapter(nextAdapter: AppNavigationAdapter): void {
|
|
161
|
+
adapter = nextAdapter
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 初始化导航,远端失败时保留当前身份最后成功配置。 */
|
|
165
|
+
export async function initializeAppNavigation(): Promise<void> {
|
|
166
|
+
const cacheKey = resolveCacheKey()
|
|
167
|
+
const cached = readCachedMenus(cacheKey)
|
|
168
|
+
useAppNavigation.setState(createNavigationState(cached ?? defaultAppMenus, false))
|
|
169
|
+
try {
|
|
170
|
+
const nextMenus = normalizeMenuResponse(await adapter.list())
|
|
171
|
+
validateMenus(nextMenus)
|
|
172
|
+
Taro.setStorageSync(cacheKey, nextMenus)
|
|
173
|
+
useAppNavigation.setState(createNavigationState(nextMenus, true))
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (!cached) console.warn('navigation fallback to local defaults', error)
|
|
176
|
+
useAppNavigation.setState((state) => ({ ...state, ready: true }))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** 使用已经校验的整份配置进行原子切换。 */
|
|
181
|
+
export function installAppNavigation(nextMenus: AppMenu[]): void {
|
|
182
|
+
validateMenus(nextMenus)
|
|
183
|
+
const snapshot = nextMenus.map((menu) => ({ ...menu }))
|
|
184
|
+
Taro.setStorageSync(resolveCacheKey(), snapshot)
|
|
185
|
+
useAppNavigation.setState(createNavigationState(snapshot, true))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 解析带路径参数和 query 的逻辑路由。 */
|
|
189
|
+
export function resolveAppRoute(rawRoute: string): ResolvedAppRoute | undefined {
|
|
190
|
+
const [rawPath, rawQuery = ''] = rawRoute.replace(/^\/+/, '').split('?', 2)
|
|
191
|
+
for (const menu of useAppNavigation.getState().menus) {
|
|
192
|
+
const params = matchLogicalPath(menu.path, rawPath)
|
|
193
|
+
if (!params) continue
|
|
194
|
+
const physicalRoute = resolveStaticView(menu.viewKey)
|
|
195
|
+
if (!physicalRoute) return
|
|
196
|
+
return {
|
|
197
|
+
menu,
|
|
198
|
+
physicalRoute,
|
|
199
|
+
query: { ...params, ...parseLogicalQuery(rawQuery) },
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** 跳转逻辑路由,并执行登录访问控制。 */
|
|
205
|
+
export function navigateAppRoute(
|
|
206
|
+
rawRoute: string,
|
|
207
|
+
options: { replace?: boolean } = {},
|
|
208
|
+
): void {
|
|
209
|
+
const resolved = resolveAppRoute(rawRoute)
|
|
210
|
+
if (!resolved) {
|
|
211
|
+
launchAppStatus('NOT_FOUND')
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
if (resolved.menu.access === 'AUTHENTICATED' && !hasValidToken()) {
|
|
215
|
+
navigateToLogin(`/pages/bootstrap/index?route=${encodeURIComponent(rawRoute)}`)
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
if (resolved.menu.access === 'GUEST_ONLY' && hasValidToken()) {
|
|
219
|
+
const home = useAppNavigation.getState().menus.find((menu) => menu.viewKey === 'HOME')
|
|
220
|
+
if (home) navigateAppRoute(home.path, { replace: true })
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
const query = Object.entries(resolved.query)
|
|
224
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
225
|
+
.join('&')
|
|
226
|
+
const url = `/${resolved.physicalRoute}${query ? `?${query}` : ''}`
|
|
227
|
+
if (options.replace || resolved.menu.inTabBar) {
|
|
228
|
+
void Taro.reLaunch({ url })
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
void Taro.navigateTo({ url, fail: () => void Taro.reLaunch({ url }) })
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 打开可替换的状态视图。 */
|
|
235
|
+
export function launchAppStatus(
|
|
236
|
+
state: import('./module').BootstrapViewKey,
|
|
237
|
+
detail = '',
|
|
238
|
+
): void {
|
|
239
|
+
const route = resolveStaticView(state) ?? 'pages/status/index'
|
|
240
|
+
const query = `state=${encodeURIComponent(state)}${detail ? `&detail=${encodeURIComponent(detail)}` : ''}`
|
|
241
|
+
void Taro.reLaunch({ url: `/${route}?${query}` })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function resolveCacheKey(): string {
|
|
245
|
+
return hasValidToken() ? AUTHENTICATED_CACHE_KEY : ANONYMOUS_CACHE_KEY
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
|
|
249
|
+
const cached = Taro.getStorageSync<unknown>(cacheKey)
|
|
250
|
+
if (!Array.isArray(cached)) return
|
|
251
|
+
try {
|
|
252
|
+
const normalized = cached.map(normalizeMenu)
|
|
253
|
+
validateMenus(normalized)
|
|
254
|
+
return normalized
|
|
255
|
+
} catch {
|
|
256
|
+
Taro.removeStorageSync(cacheKey)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function normalizeMenuResponse(response: unknown): AppMenu[] {
|
|
261
|
+
if (Array.isArray(response)) return response.map(normalizeMenu)
|
|
262
|
+
if (!response || typeof response !== 'object') throw new Error('菜单响应不是对象')
|
|
263
|
+
const record = response as Record<string, unknown>
|
|
264
|
+
const list = record.items ?? record.list ?? record.data
|
|
265
|
+
if (!Array.isArray(list)) throw new Error('菜单响应缺少扁平列表')
|
|
266
|
+
return list.map(normalizeMenu)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function normalizeMenu(value: unknown): AppMenu {
|
|
270
|
+
if (!value || typeof value !== 'object') throw new Error('菜单项不是对象')
|
|
271
|
+
const item = value as Record<string, unknown>
|
|
272
|
+
const meta = (item.meta ?? {}) as Record<string, unknown>
|
|
273
|
+
const app = (meta.app ?? item.app ?? {}) as Record<string, unknown>
|
|
274
|
+
return {
|
|
275
|
+
id: Number(item.id),
|
|
276
|
+
parentId:
|
|
277
|
+
item.parent_id === undefined && item.parentId === undefined
|
|
278
|
+
? undefined
|
|
279
|
+
: Number(item.parent_id ?? item.parentId),
|
|
280
|
+
name: String(item.name ?? ''),
|
|
281
|
+
path: String(item.path ?? ''),
|
|
282
|
+
viewKey: String(app.view_key ?? app.viewKey ?? item.view_key ?? item.viewKey ?? ''),
|
|
283
|
+
title: String(meta.title ?? item.title ?? ''),
|
|
284
|
+
access: String(app.access ?? 'AUTHENTICATED') as AppMenuAccess,
|
|
285
|
+
inTabBar: Boolean(app.in_tab_bar ?? app.inTabBar),
|
|
286
|
+
icon: typeof meta.icon === 'string' ? meta.icon : undefined,
|
|
287
|
+
selectedIcon:
|
|
288
|
+
typeof app.selected_icon === 'string'
|
|
289
|
+
? app.selected_icon
|
|
290
|
+
: typeof app.selectedIcon === 'string'
|
|
291
|
+
? app.selectedIcon
|
|
292
|
+
: undefined,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function validateMenus(nextMenus: AppMenu[]): void {
|
|
297
|
+
const ids = new Set<number>()
|
|
298
|
+
const names = new Set<string>()
|
|
299
|
+
const paths = new Set<string>()
|
|
300
|
+
for (const menu of nextMenus) {
|
|
301
|
+
if (!Number.isSafeInteger(menu.id) || !menu.name || !menu.path.startsWith('app/')) {
|
|
302
|
+
throw new Error('菜单 id、name 或 app/ 路径无效')
|
|
303
|
+
}
|
|
304
|
+
if (!menu.name.startsWith('App')) throw new Error(`菜单名称必须使用 App 前缀:${menu.name}`)
|
|
305
|
+
if (!['PUBLIC', 'GUEST_ONLY', 'AUTHENTICATED'].includes(menu.access)) {
|
|
306
|
+
throw new Error(`菜单访问模式无效:${menu.access}`)
|
|
307
|
+
}
|
|
308
|
+
if (!resolveStaticView(menu.viewKey)) throw new Error(`未注册 viewKey:${menu.viewKey}`)
|
|
309
|
+
if (ids.has(menu.id) || names.has(menu.name) || paths.has(menu.path)) {
|
|
310
|
+
throw new Error('菜单 id、name、path 必须唯一')
|
|
311
|
+
}
|
|
312
|
+
ids.add(menu.id)
|
|
313
|
+
names.add(menu.name)
|
|
314
|
+
paths.add(menu.path)
|
|
315
|
+
}
|
|
316
|
+
const menuMap = new Map(nextMenus.map((menu) => [menu.id, menu]))
|
|
317
|
+
for (const menu of nextMenus) {
|
|
318
|
+
if (menu.parentId === undefined) throw new Error(`菜单缺少父级编号:${menu.name}`)
|
|
319
|
+
if (menu.parentId === APP_MENU_ROOT_ID) {
|
|
320
|
+
if (!menu.inTabBar) throw new Error(`移动端二级菜单必须作为 tab:${menu.name}`)
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
if (menu.inTabBar) throw new Error(`只有移动端二级菜单可以作为 tab:${menu.name}`)
|
|
324
|
+
const visited = new Set<number>([menu.id])
|
|
325
|
+
let parentId = menu.parentId
|
|
326
|
+
while (parentId !== APP_MENU_ROOT_ID) {
|
|
327
|
+
if (visited.has(parentId)) throw new Error(`移动端菜单存在循环父级:${menu.name}`)
|
|
328
|
+
visited.add(parentId)
|
|
329
|
+
const parent = menuMap.get(parentId)
|
|
330
|
+
if (!parent?.parentId) throw new Error(`移动端菜单父级不存在:${menu.name}`)
|
|
331
|
+
parentId = parent.parentId
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const tabCount = nextMenus.filter((menu) => menu.parentId === APP_MENU_ROOT_ID).length
|
|
335
|
+
if (tabCount === 1 || tabCount > 5) throw new Error('tabBar 只能配置 0 或 2-5 项')
|
|
336
|
+
}
|
package/src/pages.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KratosTaroPageConfig } from './module'
|
|
2
|
+
|
|
3
|
+
/** core 页面编译配置。 */
|
|
4
|
+
export const corePages: Record<string, KratosTaroPageConfig> = {
|
|
5
|
+
'pages/index/index': {
|
|
6
|
+
style: {
|
|
7
|
+
navigationStyle: 'custom',
|
|
8
|
+
navigationBarTextStyle: 'white',
|
|
9
|
+
navigationBarTitleText: '首页',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
'pages/login/login': { style: { navigationBarTitleText: '登录' } },
|
|
13
|
+
'pages/login/protocal': { style: { navigationBarTitleText: '协议详情' } },
|
|
14
|
+
'pages/webview/webview': { style: { navigationBarTitleText: '' } },
|
|
15
|
+
'pages/status/index': {
|
|
16
|
+
style: {
|
|
17
|
+
navigationStyle: 'custom',
|
|
18
|
+
navigationBarTitleText: '',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.7.7
|
|
4
|
+
// protoc unknown
|
|
5
|
+
// source: base/v1/config.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import type { BaseConfigSite } from "../../common/v1/enum";
|
|
9
|
+
|
|
10
|
+
/** 获取系统配置条件 */
|
|
11
|
+
export interface GetConfigRequest {
|
|
12
|
+
/** 位置:枚举【BaseConfigSite】 */
|
|
13
|
+
site: BaseConfigSite;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 系统配置项 */
|
|
17
|
+
export interface ConfigItem {
|
|
18
|
+
/** 配置key */
|
|
19
|
+
key: string;
|
|
20
|
+
/** 配置value */
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 系统配置查询结果 */
|
|
25
|
+
export interface GetConfigResponse {
|
|
26
|
+
/** 系统配置列表 */
|
|
27
|
+
configs: ConfigItem[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Base系统配置公共服务 */
|
|
31
|
+
export interface ConfigService {
|
|
32
|
+
/** 获取系统配置 */
|
|
33
|
+
GetConfig(request: GetConfigRequest): Promise<GetConfigResponse>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.7.7
|
|
4
|
+
// protoc unknown
|
|
5
|
+
// source: base/v1/file.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import type { BytesValue } from "../../google/protobuf/wrappers";
|
|
9
|
+
|
|
10
|
+
/** 批量上传文件请求参数 */
|
|
11
|
+
export interface MultiUploadFileRequest {
|
|
12
|
+
/** 文件名 */
|
|
13
|
+
files: UploadFileInfo[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 批量上传文件响应 */
|
|
17
|
+
export interface MultiUploadFileResponse {
|
|
18
|
+
/** 文件上传结果 */
|
|
19
|
+
files: FileInfo[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 单文件上传请求参数 */
|
|
23
|
+
export interface UploadFileRequest {
|
|
24
|
+
/** 上传文件信息 */
|
|
25
|
+
file: UploadFileInfo | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 上传文件信息 */
|
|
29
|
+
export interface UploadFileInfo {
|
|
30
|
+
/** 文件名 */
|
|
31
|
+
name: string;
|
|
32
|
+
/** 文件扩展名 */
|
|
33
|
+
extname: string;
|
|
34
|
+
/** 文件路径 */
|
|
35
|
+
path: string;
|
|
36
|
+
/** 二进制内容将通过 base64 编码传输 */
|
|
37
|
+
content: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 下载文件请求参数 */
|
|
41
|
+
export interface DownloadFileRequest {
|
|
42
|
+
/** 文件名 */
|
|
43
|
+
name: string;
|
|
44
|
+
/** 文件路径 */
|
|
45
|
+
path: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** File信息 */
|
|
49
|
+
export interface FileInfo {
|
|
50
|
+
/** 文件路径 */
|
|
51
|
+
url: string;
|
|
52
|
+
/** 文件名 */
|
|
53
|
+
name: string;
|
|
54
|
+
/** 文件扩展名 */
|
|
55
|
+
extname: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Base文件服务 */
|
|
59
|
+
export interface FileService {
|
|
60
|
+
/** 多个文件上传 */
|
|
61
|
+
MultiUploadFile(request: MultiUploadFileRequest): Promise<MultiUploadFileResponse>;
|
|
62
|
+
/** 单个文件上传 */
|
|
63
|
+
UploadFile(request: UploadFileRequest): Promise<FileInfo>;
|
|
64
|
+
/** 下载文件 */
|
|
65
|
+
DownloadFile(request: DownloadFileRequest): Promise<BytesValue>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.7.7
|
|
4
|
+
// protoc unknown
|
|
5
|
+
// source: base/v1/login.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import type { PasswordCryptoScene } from "../../common/v1/enum";
|
|
9
|
+
import type { PasswordCrypto } from "../../common/v1/types";
|
|
10
|
+
import type { Empty } from "../../google/protobuf/empty";
|
|
11
|
+
|
|
12
|
+
/** 验证码获取条件 */
|
|
13
|
+
export interface CaptchaRequest {
|
|
14
|
+
/** 验证码类型,random 表示随机选择当前支持的验证码类型 */
|
|
15
|
+
type: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 密码临时公钥获取条件 */
|
|
19
|
+
export interface PasswordPublicKeyRequest {
|
|
20
|
+
/** 使用场景 */
|
|
21
|
+
scene: PasswordCryptoScene;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 登出请求条件 */
|
|
25
|
+
export interface LogoutRequest {
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 验证码响应 */
|
|
29
|
+
export interface CaptchaResponse {
|
|
30
|
+
/** 验证码ID。 */
|
|
31
|
+
captcha_id: string;
|
|
32
|
+
/** 验证码base64 */
|
|
33
|
+
captcha_base64: string;
|
|
34
|
+
/** 本次验证码实际类型 */
|
|
35
|
+
type: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 验证码预校验请求 */
|
|
39
|
+
export interface VerifyCaptchaRequest {
|
|
40
|
+
/** 验证码ID */
|
|
41
|
+
captcha_id: string;
|
|
42
|
+
/** 验证码答案 */
|
|
43
|
+
captcha_code: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 验证码预校验响应 */
|
|
47
|
+
export interface VerifyCaptchaResponse {
|
|
48
|
+
/** 验证码通过令牌 */
|
|
49
|
+
captcha_token: string;
|
|
50
|
+
/** 有效时间,单位秒 */
|
|
51
|
+
expires_in: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 密码临时公钥响应 */
|
|
55
|
+
export interface PasswordPublicKeyResponse {
|
|
56
|
+
/** 临时密钥ID */
|
|
57
|
+
key_id: string;
|
|
58
|
+
/** 临时公钥 */
|
|
59
|
+
public_key: string;
|
|
60
|
+
/** 加密算法 */
|
|
61
|
+
algorithm: string;
|
|
62
|
+
/** 随机值 */
|
|
63
|
+
nonce: string;
|
|
64
|
+
/** 有效时间,单位秒 */
|
|
65
|
+
expires_in: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 刷新令牌请求参数 */
|
|
69
|
+
export interface RefreshTokenRequest {
|
|
70
|
+
/** 更新令牌,用来获取下一次的访问令牌,必选项。 */
|
|
71
|
+
refresh_token: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 刷新令牌响应 */
|
|
75
|
+
export interface RefreshTokenResponse {
|
|
76
|
+
/** 访问令牌,必选项。 */
|
|
77
|
+
access_token: string;
|
|
78
|
+
/** 更新令牌,用来获取下一次的访问令牌,可选项。 */
|
|
79
|
+
refresh_token: string;
|
|
80
|
+
/** 令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 */
|
|
81
|
+
token_type: string;
|
|
82
|
+
/** 令牌有效时间,单位为秒。如果访问令牌过期,服务器应回复授予访问令牌的持续时间。如果省略该参数,必须其他方式设置过期时间。 */
|
|
83
|
+
expires_in: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 登录请求参数 */
|
|
87
|
+
export interface LoginRequest {
|
|
88
|
+
/** 租户编码 */
|
|
89
|
+
tenant_code: string;
|
|
90
|
+
/** 用户名,必选项。 */
|
|
91
|
+
user_name: string;
|
|
92
|
+
/** 用户的密码,必选项。 */
|
|
93
|
+
password:
|
|
94
|
+
| PasswordCrypto
|
|
95
|
+
| undefined;
|
|
96
|
+
/** 验证码 */
|
|
97
|
+
captcha_code: string;
|
|
98
|
+
/** 验证码Id */
|
|
99
|
+
captcha_id: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 登录响应 */
|
|
103
|
+
export interface LoginResponse {
|
|
104
|
+
/** 访问令牌,必选项。 */
|
|
105
|
+
access_token: string;
|
|
106
|
+
/** 更新令牌,用来获取下一次的访问令牌,可选项。 */
|
|
107
|
+
refresh_token: string;
|
|
108
|
+
/** 令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 */
|
|
109
|
+
token_type: string;
|
|
110
|
+
/** 令牌有效时间,单位为秒。如果访问令牌过期,服务器应回复授予访问令牌的持续时间。如果省略该参数,必须其他方式设置过期时间。 */
|
|
111
|
+
expires_in: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Base登录公共服务 */
|
|
115
|
+
export interface LoginService {
|
|
116
|
+
/** 验证码 */
|
|
117
|
+
Captcha(request: CaptchaRequest): Promise<CaptchaResponse>;
|
|
118
|
+
/** 验证码预校验 */
|
|
119
|
+
VerifyCaptcha(request: VerifyCaptchaRequest): Promise<VerifyCaptchaResponse>;
|
|
120
|
+
/** 获取密码临时公钥 */
|
|
121
|
+
PasswordPublicKey(request: PasswordPublicKeyRequest): Promise<PasswordPublicKeyResponse>;
|
|
122
|
+
/** 登出 */
|
|
123
|
+
Logout(request: LogoutRequest): Promise<Empty>;
|
|
124
|
+
/** 刷新认证令牌 */
|
|
125
|
+
RefreshToken(request: RefreshTokenRequest): Promise<RefreshTokenResponse>;
|
|
126
|
+
/** 登录 */
|
|
127
|
+
Login(request: LoginRequest): Promise<LoginResponse>;
|
|
128
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.7.7
|
|
4
|
+
// protoc unknown
|
|
5
|
+
// source: base/v1/mcp.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import type { Empty } from "../../google/protobuf/empty";
|
|
9
|
+
|
|
10
|
+
/** MCP请求参数 */
|
|
11
|
+
export interface HandleMcpRequest {
|
|
12
|
+
/** 服务筛选关键字,必填,支持 app、admin 或任意服务关键字 */
|
|
13
|
+
terminal: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Base MCP服务 */
|
|
17
|
+
export interface McpService {
|
|
18
|
+
/** 处理MCP Streamable HTTP请求 */
|
|
19
|
+
HandleMcp(request: HandleMcpRequest): Promise<Empty>;
|
|
20
|
+
}
|