@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.
Files changed (77) hide show
  1. package/README.md +36 -0
  2. package/dist/build.mjs +38 -0
  3. package/dist/runner.mjs +321 -0
  4. package/package.json +51 -0
  5. package/src/api/base/config.ts +20 -0
  6. package/src/api/base/file.ts +57 -0
  7. package/src/api/base/login.ts +87 -0
  8. package/src/api/base/menu.ts +17 -0
  9. package/src/api/base/oauth.ts +149 -0
  10. package/src/api/system/auth.ts +54 -0
  11. package/src/api/system/base_area.ts +34 -0
  12. package/src/api/system/base_dict.ts +24 -0
  13. package/src/bootstrap.ts +32 -0
  14. package/src/build.ts +24 -0
  15. package/src/components/BootstrapStatus.scss +28 -0
  16. package/src/components/BootstrapStatus.tsx +25 -0
  17. package/src/components/KratosPageFrame.scss +41 -0
  18. package/src/components/KratosPageFrame.tsx +51 -0
  19. package/src/components/KratosTabBar.scss +34 -0
  20. package/src/components/KratosTabBar.tsx +45 -0
  21. package/src/index.ts +36 -0
  22. package/src/module.ts +63 -0
  23. package/src/navigation-pattern.d.mts +2 -0
  24. package/src/navigation-pattern.mjs +29 -0
  25. package/src/navigation-tree.d.mts +12 -0
  26. package/src/navigation-tree.mjs +45 -0
  27. package/src/navigation.ts +336 -0
  28. package/src/pages.ts +21 -0
  29. package/src/rpc/base/v1/config.ts +34 -0
  30. package/src/rpc/base/v1/file.ts +66 -0
  31. package/src/rpc/base/v1/login.ts +128 -0
  32. package/src/rpc/base/v1/mcp.ts +20 -0
  33. package/src/rpc/base/v1/oauth.ts +199 -0
  34. package/src/rpc/base/v1/sse.ts +22 -0
  35. package/src/rpc/common/v1/common.ts +71 -0
  36. package/src/rpc/common/v1/enum.ts +71 -0
  37. package/src/rpc/common/v1/types.ts +77 -0
  38. package/src/rpc/google/protobuf/empty.ts +19 -0
  39. package/src/rpc/google/protobuf/wrappers.ts +124 -0
  40. package/src/rpc/system/app/v1/auth.ts +54 -0
  41. package/src/rpc/system/app/v1/base_area.ts +24 -0
  42. package/src/rpc/system/app/v1/base_dict.ts +37 -0
  43. package/src/rpc/system/app/v1/base_menu.ts +61 -0
  44. package/src/rpc/system/common/v1/enum.ts +80 -0
  45. package/src/runner.ts +390 -0
  46. package/src/static/images/avatar.png +0 -0
  47. package/src/static/images/center_bg.png +0 -0
  48. package/src/static/images/logo_icon.png +0 -0
  49. package/src/static/images/navigator_bg.png +0 -0
  50. package/src/static/tabs/home_default.png +0 -0
  51. package/src/static/tabs/home_selected.png +0 -0
  52. package/src/static/tabs/user_default.png +0 -0
  53. package/src/static/tabs/user_selected.png +0 -0
  54. package/src/stores/index.ts +2 -0
  55. package/src/stores/setting.ts +43 -0
  56. package/src/stores/user.ts +152 -0
  57. package/src/styles/base.scss +45 -0
  58. package/src/types/assets.d.ts +9 -0
  59. package/src/utils/auth.ts +55 -0
  60. package/src/utils/file.ts +48 -0
  61. package/src/utils/http.ts +262 -0
  62. package/src/utils/index.ts +35 -0
  63. package/src/utils/navigation.ts +72 -0
  64. package/src/utils/passwordCrypto.ts +257 -0
  65. package/src/views/pages/index/index.scss +137 -0
  66. package/src/views/pages/index/index.tsx +74 -0
  67. package/src/views/pages/login/components/BehaviorCaptcha.h5.tsx +46 -0
  68. package/src/views/pages/login/components/BehaviorCaptcha.tsx +6 -0
  69. package/src/views/pages/login/components/BehaviorCaptcha.weapp.tsx +6 -0
  70. package/src/views/pages/login/components/types.ts +32 -0
  71. package/src/views/pages/login/login.scss +310 -0
  72. package/src/views/pages/login/login.tsx +493 -0
  73. package/src/views/pages/login/protocal.scss +84 -0
  74. package/src/views/pages/login/protocal.tsx +73 -0
  75. package/src/views/pages/status/index.tsx +42 -0
  76. package/src/views/pages/webview/webview.scss +43 -0
  77. package/src/views/pages/webview/webview.tsx +41 -0
@@ -0,0 +1,61 @@
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: system/app/v1/base_menu.proto
6
+
7
+ /* eslint-disable */
8
+
9
+ /** 移动端菜单查询条件 */
10
+ export interface ListBaseMenuRequest {
11
+ }
12
+
13
+ /** 移动端菜单响应 */
14
+ export interface ListBaseMenuResponse {
15
+ /** 移动端扁平菜单 */
16
+ items: BaseMenu[];
17
+ }
18
+
19
+ /** 移动端菜单 */
20
+ export interface BaseMenu {
21
+ /** 菜单ID */
22
+ id: number;
23
+ /** 父级菜单ID */
24
+ parent_id: number;
25
+ /** 逻辑路由地址 */
26
+ path: string;
27
+ /** 逻辑路由名称 */
28
+ name: string;
29
+ /** 移动端菜单元信息 */
30
+ meta: BaseMenuMeta | undefined;
31
+ }
32
+
33
+ /** 移动端菜单元信息 */
34
+ export interface BaseMenuMeta {
35
+ /** 菜单标题 */
36
+ title: string;
37
+ /** 菜单图标 */
38
+ icon?:
39
+ | string
40
+ | undefined;
41
+ /** 移动端页面配置 */
42
+ app: BaseMenuAppMeta | undefined;
43
+ }
44
+
45
+ /** 移动端页面配置 */
46
+ export interface BaseMenuAppMeta {
47
+ /** 已注册的稳定视图键 */
48
+ view_key: string;
49
+ /** 页面访问模式 */
50
+ access: string;
51
+ /** 是否显示在自绘标签栏 */
52
+ in_tab_bar: boolean;
53
+ /** 标签栏选中图标 */
54
+ selected_icon?: string | undefined;
55
+ }
56
+
57
+ /** App菜单服务 */
58
+ export interface BaseMenuService {
59
+ /** 查询移动端菜单 */
60
+ ListBaseMenu(request: ListBaseMenuRequest): Promise<ListBaseMenuResponse>;
61
+ }
@@ -0,0 +1,80 @@
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: system/common/v1/enum.proto
6
+
7
+ /* eslint-disable */
8
+
9
+ /** 系统配置类型 */
10
+ export enum BaseConfigType {
11
+ UNKNOWN_BCT = 0,
12
+ /** TEXT - 文本 */
13
+ TEXT = 1,
14
+ /** IMAGE - 图片 */
15
+ IMAGE = 2,
16
+ /** RICH_TEXT - 富文本 */
17
+ RICH_TEXT = 3,
18
+ /** DICT - 字典 */
19
+ DICT = 4,
20
+ /** BOOLEAN - 布尔 */
21
+ BOOLEAN = 5,
22
+ }
23
+
24
+ /** 定时任务日志状态 */
25
+ export enum BaseJobLogStatus {
26
+ UNKNOWN_BJLS = 0,
27
+ /** SUCCESS - 成功 */
28
+ SUCCESS = 1,
29
+ /** FAIL - 失败 */
30
+ FAIL = 2,
31
+ }
32
+
33
+ /** 菜单类型 */
34
+ export enum BaseMenuType {
35
+ UNKNOWN_MT = 0,
36
+ /** FOLDER - 目录 */
37
+ FOLDER = 1,
38
+ /** MENU - 菜单 */
39
+ MENU = 2,
40
+ /** BUTTON - 按钮 */
41
+ BUTTON = 3,
42
+ /** EXT_LINK - 外部链接 */
43
+ EXT_LINK = 4,
44
+ }
45
+
46
+ /** 角色数据范围 */
47
+ export enum BaseRoleDataScope {
48
+ UNKNOWN_RDS = 0,
49
+ /** ALL - 全部数据 */
50
+ ALL = 1,
51
+ /** DEPT_AND_CHILDREN - 部门及子部门数据 */
52
+ DEPT_AND_CHILDREN = 2,
53
+ /** SELF_DEPT - 本部门数据 */
54
+ SELF_DEPT = 3,
55
+ /** SELF_USER - 本人数据 */
56
+ SELF_USER = 4,
57
+ }
58
+
59
+ /** 用户性别 */
60
+ export enum BaseUserGender {
61
+ UNKNOWN_BUG = 0,
62
+ /** SECRET - 保密 */
63
+ SECRET = 1,
64
+ /** BOY - 男 */
65
+ BOY = 2,
66
+ /** GIRL - 禁用 */
67
+ GIRL = 3,
68
+ }
69
+
70
+ /** 代码生成表状态 */
71
+ export enum CodeGenTableStatus {
72
+ /** UNKNOWN_CGTS - 未指定 */
73
+ UNKNOWN_CGTS = 0,
74
+ /** DRAFT_CGTS - 草稿 */
75
+ DRAFT_CGTS = 1,
76
+ /** GENERATED_CGTS - 已生成 */
77
+ GENERATED_CGTS = 2,
78
+ /** DISABLED_CGTS - 停用 */
79
+ DISABLED_CGTS = 3,
80
+ }
package/src/runner.ts ADDED
@@ -0,0 +1,390 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ rmdirSync,
8
+ statSync,
9
+ unlinkSync,
10
+ writeFileSync,
11
+ } from 'node:fs'
12
+ import { createRequire } from 'node:module'
13
+ import { dirname, relative, resolve } from 'node:path'
14
+ import { pathToFileURL } from 'node:url'
15
+ import { spawn } from 'node:child_process'
16
+ import ts from 'typescript'
17
+ import type { KratosTaroBuildModule } from './build'
18
+
19
+ type PageEntry = {
20
+ route: string
21
+ source: string
22
+ style?: Record<string, unknown>
23
+ }
24
+
25
+ type AppManifest = {
26
+ pages: string[]
27
+ subpackages?: Array<{ root: string; pages: string[] }>
28
+ [key: string]: unknown
29
+ }
30
+
31
+ type BuildTransaction = {
32
+ inputDir: string
33
+ appConfigFile: string
34
+ originalAppConfig: string
35
+ generatedFiles: string[]
36
+ generatedStaticFiles: string[]
37
+ ownerPid: number
38
+ }
39
+
40
+ type CliOptions = {
41
+ type: 'h5' | 'weapp'
42
+ mode: string
43
+ watch: boolean
44
+ }
45
+
46
+ /** 运行 Taro 构建并在进程结束后恢复临时页面。 */
47
+ async function main(): Promise<void> {
48
+ const options = parseArguments(process.argv.slice(2))
49
+ const hostRoot = process.cwd()
50
+ const inputDir = resolve(hostRoot, 'src')
51
+ const appConfigFile = resolve(inputDir, 'app.config.ts')
52
+ const baseConfigFile = resolve(inputDir, 'app.config.base.json')
53
+ const transactionFile = resolve(hostRoot, '.kratos-taro-app-pages-state.json')
54
+ recoverBuildTransaction(transactionFile, inputDir, appConfigFile)
55
+
56
+ const originalAppConfig = readFileSync(appConfigFile, 'utf8')
57
+ const manifest = JSON.parse(readFileSync(baseConfigFile, 'utf8')) as AppManifest
58
+ const modules = await loadBuildModules(hostRoot, resolve(inputDir, 'module-manifest.ts'))
59
+ const pageMap = new Map<string, PageEntry>()
60
+ modules.forEach((module) => {
61
+ scanViews(resolve(module.root, 'src/views')).forEach((page) => {
62
+ const config = module.pages[page.route] ?? {}
63
+ const route = normalizeRoute(config.route ?? page.route)
64
+ pageMap.set(route, {
65
+ route,
66
+ source: `${module.name}/views/${page.route}`,
67
+ style: config.style,
68
+ })
69
+ })
70
+ })
71
+
72
+ const generatedFiles = [...pageMap.values()].flatMap((page) => {
73
+ const pageFile = resolve(inputDir, `${page.route}.tsx`)
74
+ const configFile = resolve(inputDir, `${page.route}.config.ts`)
75
+ return [pageFile, configFile].filter((file) => !existsSync(file))
76
+ })
77
+ const staticRoot = resolve(inputDir, 'static')
78
+ const staticFiles = collectModuleStaticFiles(
79
+ modules.map((module) => resolve(module.root, 'src/static')),
80
+ staticRoot,
81
+ )
82
+ const generatedStaticFiles = staticFiles.map(({ target }) => target)
83
+ const transaction: BuildTransaction = {
84
+ inputDir,
85
+ appConfigFile,
86
+ originalAppConfig,
87
+ generatedFiles,
88
+ generatedStaticFiles,
89
+ ownerPid: process.pid,
90
+ }
91
+ writeFileSync(transactionFile, `${JSON.stringify(transaction, null, 2)}\n`, { flag: 'wx' })
92
+
93
+ let cleaned = false
94
+ const cleanup = () => {
95
+ if (cleaned) return
96
+ cleaned = true
97
+ cleanupTransaction(transaction)
98
+ if (existsSync(transactionFile)) unlinkSync(transactionFile)
99
+ }
100
+ const handleSignal = (signal: NodeJS.Signals) => {
101
+ child?.kill(signal)
102
+ }
103
+ let child: ReturnType<typeof spawn> | undefined
104
+ process.once('SIGINT', () => handleSignal('SIGINT'))
105
+ process.once('SIGTERM', () => handleSignal('SIGTERM'))
106
+
107
+ try {
108
+ pageMap.forEach((page) => {
109
+ appendPage(manifest, page.route)
110
+ const pageFile = resolve(inputDir, `${page.route}.tsx`)
111
+ const configFile = resolve(inputDir, `${page.route}.config.ts`)
112
+ if (!existsSync(pageFile)) {
113
+ mkdirSync(dirname(pageFile), { recursive: true })
114
+ writeFileSync(pageFile, createPageWrapper(page))
115
+ }
116
+ if (!existsSync(configFile)) {
117
+ writeFileSync(configFile, createPageConfig(page))
118
+ }
119
+ })
120
+ staticFiles.forEach(({ source, target }) => {
121
+ mkdirSync(dirname(target), { recursive: true })
122
+ copyFileSync(source, target)
123
+ })
124
+ writeFileSync(
125
+ appConfigFile,
126
+ `export default defineAppConfig(${JSON.stringify(manifest, null, 2)})\n`,
127
+ )
128
+
129
+ const args = ['exec', 'taro', 'build', '--type', options.type, '--mode', options.mode]
130
+ if (options.watch) args.push('--watch')
131
+ child = spawn(process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm', args, {
132
+ cwd: hostRoot,
133
+ env: {
134
+ ...process.env,
135
+ NODE_ENV: options.mode === 'development' ? 'development' : 'production',
136
+ },
137
+ stdio: 'inherit',
138
+ })
139
+ const exitCode = await new Promise<number>((complete, reject) => {
140
+ child?.once('error', reject)
141
+ child?.once('exit', (code, signal) => {
142
+ if (signal) {
143
+ complete(signal === 'SIGINT' ? 130 : 143)
144
+ return
145
+ }
146
+ complete(code ?? 1)
147
+ })
148
+ })
149
+ if (exitCode !== 0) process.exitCode = exitCode
150
+ } finally {
151
+ cleanup()
152
+ }
153
+ }
154
+
155
+ function parseArguments(args: string[]): CliOptions {
156
+ let type: CliOptions['type'] | undefined
157
+ let mode = 'production'
158
+ let watch = false
159
+ for (let index = 0; index < args.length; index += 1) {
160
+ const argument = args[index]
161
+ if (argument === '--type') {
162
+ const value = args[++index]
163
+ if (value !== 'h5' && value !== 'weapp') throw new Error(`不支持的 Taro 类型:${value}`)
164
+ type = value
165
+ } else if (argument === '--mode') {
166
+ mode = args[++index] || mode
167
+ } else if (argument === '--watch') {
168
+ watch = true
169
+ } else {
170
+ throw new Error(`未知参数:${argument}`)
171
+ }
172
+ }
173
+ if (!type) throw new Error('缺少 --type h5|weapp')
174
+ return { type, mode, watch }
175
+ }
176
+
177
+ async function loadBuildModules(
178
+ hostRoot: string,
179
+ manifestFile: string,
180
+ ): Promise<KratosTaroBuildModule[]> {
181
+ const source = readFileSync(manifestFile, 'utf8')
182
+ const sourceFile = ts.createSourceFile(
183
+ manifestFile,
184
+ source,
185
+ ts.ScriptTarget.Latest,
186
+ true,
187
+ ts.ScriptKind.TS,
188
+ )
189
+ const imports = new Map<string, string>()
190
+ let moduleNames: string[] = []
191
+ sourceFile.forEachChild((node) => {
192
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
193
+ const importClause = node.importClause
194
+ const bindings = node.importClause?.namedBindings
195
+ const packageName = node.moduleSpecifier.text
196
+ if (importClause?.name) imports.set(importClause.name.text, packageName)
197
+ if (bindings && ts.isNamedImports(bindings)) {
198
+ bindings.elements.forEach((element) => {
199
+ imports.set(element.name.text, packageName)
200
+ })
201
+ }
202
+ return
203
+ }
204
+ if (!ts.isVariableStatement(node)) return
205
+ node.declarationList.declarations.forEach((declaration) => {
206
+ if (
207
+ ts.isIdentifier(declaration.name) &&
208
+ declaration.name.text === 'moduleManifest' &&
209
+ declaration.initializer &&
210
+ ts.isArrayLiteralExpression(declaration.initializer)
211
+ ) {
212
+ moduleNames = declaration.initializer.elements
213
+ .filter(ts.isIdentifier)
214
+ .map((element) => element.text)
215
+ }
216
+ })
217
+ })
218
+ if (!moduleNames.length) throw new Error(`模块清单未导出 moduleManifest:${manifestFile}`)
219
+
220
+ const hostRequire = createRequire(resolve(hostRoot, 'package.json'))
221
+ return Promise.all(
222
+ moduleNames.map(async (name) => {
223
+ const packageName = imports.get(name)
224
+ if (!packageName) throw new Error(`模块 ${name} 缺少静态 import`)
225
+ const buildEntry = hostRequire.resolve(`${packageName}/build`)
226
+ const loaded = (await import(pathToFileURL(buildEntry).href)) as {
227
+ buildModule?: KratosTaroBuildModule
228
+ }
229
+ if (!loaded.buildModule) throw new Error(`${packageName}/build 未导出 buildModule`)
230
+ return loaded.buildModule
231
+ }),
232
+ )
233
+ }
234
+
235
+ function recoverBuildTransaction(
236
+ transactionFile: string,
237
+ inputDir: string,
238
+ appConfigFile: string,
239
+ ): void {
240
+ if (!existsSync(transactionFile)) return
241
+ const transaction = JSON.parse(readFileSync(transactionFile, 'utf8')) as BuildTransaction
242
+ if (transaction.inputDir !== inputDir || transaction.appConfigFile !== appConfigFile) {
243
+ throw new Error(`Taro 构建事务目录不匹配:${transactionFile}`)
244
+ }
245
+ if (transaction.ownerPid !== process.pid && isProcessRunning(transaction.ownerPid)) {
246
+ throw new Error(
247
+ `页面装配已由进程 ${transaction.ownerPid} 使用,请先停止另一个 H5 或小程序开发进程`,
248
+ )
249
+ }
250
+ cleanupTransaction(transaction)
251
+ unlinkSync(transactionFile)
252
+ }
253
+
254
+ function cleanupTransaction(transaction: BuildTransaction): void {
255
+ for (const file of [...transaction.generatedFiles].reverse()) {
256
+ if (existsSync(file)) unlinkSync(file)
257
+ removeEmptyParents(dirname(file), transaction.inputDir)
258
+ }
259
+ removeGeneratedStaticFiles(transaction.generatedStaticFiles, resolve(transaction.inputDir, 'static'))
260
+ writeFileSync(transaction.appConfigFile, transaction.originalAppConfig)
261
+ }
262
+
263
+ function isProcessRunning(pid: number): boolean {
264
+ try {
265
+ process.kill(pid, 0)
266
+ return true
267
+ } catch (error) {
268
+ return (error as NodeJS.ErrnoException).code === 'EPERM'
269
+ }
270
+ }
271
+
272
+ function scanViews(root: string): Array<{ route: string; source: string }> {
273
+ if (!existsSync(root)) return []
274
+ const files: string[] = []
275
+ const visit = (directory: string) => {
276
+ readdirSync(directory).forEach((entry) => {
277
+ const path = resolve(directory, entry)
278
+ if (statSync(path).isDirectory()) {
279
+ if (entry !== 'components') visit(path)
280
+ } else if (entry.endsWith('.tsx')) {
281
+ files.push(path)
282
+ }
283
+ })
284
+ }
285
+ visit(root)
286
+ return files.map((source) => ({
287
+ route: normalizeRoute(relative(root, source)),
288
+ source,
289
+ }))
290
+ }
291
+
292
+ function collectModuleStaticFiles(
293
+ sourceRoots: string[],
294
+ targetRoot: string,
295
+ ): Array<{ source: string; target: string }> {
296
+ const fileMap = new Map<string, { source: string; target: string }>()
297
+ sourceRoots.forEach((sourceRoot) => {
298
+ scanFiles(sourceRoot).forEach((source) => {
299
+ const target = resolve(targetRoot, relative(sourceRoot, source))
300
+ if (!existsSync(target)) fileMap.set(target, { source, target })
301
+ })
302
+ })
303
+ return [...fileMap.values()]
304
+ }
305
+
306
+ function scanFiles(root: string): string[] {
307
+ if (!existsSync(root)) return []
308
+ const files: string[] = []
309
+ const visit = (directory: string) => {
310
+ readdirSync(directory).forEach((entry) => {
311
+ const path = resolve(directory, entry)
312
+ if (statSync(path).isDirectory()) visit(path)
313
+ else files.push(path)
314
+ })
315
+ }
316
+ visit(root)
317
+ return files
318
+ }
319
+
320
+ function appendPage(manifest: AppManifest, route: string): void {
321
+ const [first, ...rest] = route.split('/')
322
+ if (!first.startsWith('pages') || first === 'pages') {
323
+ if (!manifest.pages.includes(route)) manifest.pages.push(route)
324
+ return
325
+ }
326
+ manifest.subpackages ??= []
327
+ let subpackage = manifest.subpackages.find((item) => item.root === first)
328
+ if (!subpackage) {
329
+ subpackage = { root: first, pages: [] }
330
+ manifest.subpackages.push(subpackage)
331
+ }
332
+ const subRoute = rest.join('/')
333
+ if (!subpackage.pages.includes(subRoute)) subpackage.pages.push(subRoute)
334
+ }
335
+
336
+ function createPageWrapper(page: PageEntry): string {
337
+ return `import KratosPage from ${JSON.stringify(page.source)}
338
+ import { KratosPageFrame } from '@liujitcn/kratos-taro-app-core/components/KratosPageFrame'
339
+ import { KratosTabBar } from '@liujitcn/kratos-taro-app-core/components/KratosTabBar'
340
+
341
+ /** 自动生成的模块页面包装器。 */
342
+ export default function KratosPageWrapper() {
343
+ return (
344
+ <>
345
+ <KratosPageFrame
346
+ navigationStyle=${JSON.stringify(page.style?.navigationStyle ?? 'default')}
347
+ navigationBarTitleText=${JSON.stringify(page.style?.navigationBarTitleText ?? '')}
348
+ navigationBarBackgroundColor=${JSON.stringify(page.style?.navigationBarBackgroundColor ?? '#f8f8f8')}
349
+ navigationBarTextStyle=${JSON.stringify(page.style?.navigationBarTextStyle ?? 'black')}
350
+ >
351
+ <KratosPage />
352
+ </KratosPageFrame>
353
+ <KratosTabBar route=${JSON.stringify(page.route)} />
354
+ </>
355
+ )
356
+ }
357
+ `
358
+ }
359
+
360
+ function createPageConfig(page: PageEntry): string {
361
+ return `export default definePageConfig(${JSON.stringify(page.style ?? {}, null, 2)})\n`
362
+ }
363
+
364
+ function normalizeRoute(route: string): string {
365
+ return route
366
+ .replace(/\\/g, '/')
367
+ .replace(/^\/+/, '')
368
+ .replace(/\.tsx$/, '')
369
+ }
370
+
371
+ function removeGeneratedStaticFiles(files: string[], staticRoot: string): void {
372
+ for (const file of [...files].reverse()) {
373
+ if (existsSync(file)) unlinkSync(file)
374
+ removeEmptyParents(dirname(file), staticRoot)
375
+ }
376
+ }
377
+
378
+ function removeEmptyParents(directory: string, boundary: string): void {
379
+ let current = directory
380
+ while (current !== boundary && current.startsWith(boundary) && existsSync(current)) {
381
+ if (readdirSync(current).length) return
382
+ rmdirSync(current)
383
+ current = dirname(current)
384
+ }
385
+ }
386
+
387
+ void main().catch((error: unknown) => {
388
+ console.error(error)
389
+ process.exitCode = 1
390
+ })
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,2 @@
1
+ export * from './setting'
2
+ export * from './user'
@@ -0,0 +1,43 @@
1
+ import { create } from 'zustand'
2
+ import { defConfigService } from '../api/base/config'
3
+ import { BaseConfigSite } from '../rpc/common/v1/enum'
4
+
5
+ const REQUIRED_APP_CONFIGS = [
6
+ { key: 'serviceProtocol', name: '服务条款' },
7
+ { key: 'privacyProtocol', name: '隐私协议' },
8
+ { key: 'captchaType', name: '登录验证码类型' },
9
+ ] as const
10
+
11
+ /** 应用配置状态及操作。 */
12
+ export interface SettingStoreState {
13
+ data?: Map<string, string>
14
+ getData: (key: string) => string | undefined
15
+ loadData: () => Promise<void>
16
+ }
17
+
18
+ let loading: Promise<void> | undefined
19
+
20
+ /** 应用配置 Zustand Store。 */
21
+ export const useSettingStore = create<SettingStoreState>((set, get) => ({
22
+ data: undefined,
23
+ getData(key) {
24
+ return get().data?.get(key)
25
+ },
26
+ async loadData() {
27
+ if (loading) return loading
28
+ loading = (async () => {
29
+ const response = await defConfigService.GetConfig({ site: BaseConfigSite.APP })
30
+ const nextData = new Map(response.configs.map((item) => [item.key, item.value]))
31
+ const missing = REQUIRED_APP_CONFIGS.filter(({ key }) => !nextData.get(key))
32
+ if (missing.length) {
33
+ throw new Error(`移动端配置缺失:${missing.map(({ name }) => name).join('、')}`)
34
+ }
35
+ set({ data: nextData })
36
+ })()
37
+ try {
38
+ await loading
39
+ } finally {
40
+ loading = undefined
41
+ }
42
+ },
43
+ }))