@liujitcn/kratos-uni-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 (66) hide show
  1. package/README.md +46 -0
  2. package/dist/module.mjs +84 -0
  3. package/dist/vite.mjs +405 -0
  4. package/package.json +58 -0
  5. package/src/api/base/config.ts +20 -0
  6. package/src/api/base/file.ts +79 -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 +23 -0
  14. package/src/components/BootstrapStatus.vue +39 -0
  15. package/src/components/KratosTabBar.vue +73 -0
  16. package/src/index.ts +6 -0
  17. package/src/module.ts +85 -0
  18. package/src/navigation-pattern.d.mts +2 -0
  19. package/src/navigation-pattern.mjs +29 -0
  20. package/src/navigation-tree.mjs +45 -0
  21. package/src/navigation.ts +330 -0
  22. package/src/pages.ts +26 -0
  23. package/src/rpc/base/v1/config.ts +34 -0
  24. package/src/rpc/base/v1/file.ts +66 -0
  25. package/src/rpc/base/v1/login.ts +128 -0
  26. package/src/rpc/base/v1/mcp.ts +20 -0
  27. package/src/rpc/base/v1/oauth.ts +199 -0
  28. package/src/rpc/base/v1/sse.ts +22 -0
  29. package/src/rpc/common/v1/common.ts +71 -0
  30. package/src/rpc/common/v1/enum.ts +71 -0
  31. package/src/rpc/common/v1/types.ts +77 -0
  32. package/src/rpc/google/protobuf/empty.ts +19 -0
  33. package/src/rpc/google/protobuf/wrappers.ts +124 -0
  34. package/src/rpc/system/app/v1/auth.ts +54 -0
  35. package/src/rpc/system/app/v1/base_area.ts +24 -0
  36. package/src/rpc/system/app/v1/base_dict.ts +37 -0
  37. package/src/rpc/system/app/v1/base_menu.ts +61 -0
  38. package/src/rpc/system/common/v1/enum.ts +80 -0
  39. package/src/static/images/avatar.png +0 -0
  40. package/src/static/images/center_bg.png +0 -0
  41. package/src/static/images/logo_icon.png +0 -0
  42. package/src/static/images/navigator_bg.png +0 -0
  43. package/src/static/tabs/home_default.png +0 -0
  44. package/src/static/tabs/home_selected.png +0 -0
  45. package/src/static/tabs/user_default.png +0 -0
  46. package/src/static/tabs/user_selected.png +0 -0
  47. package/src/stores/index.ts +14 -0
  48. package/src/stores/modules/setting.ts +56 -0
  49. package/src/stores/modules/user.ts +266 -0
  50. package/src/styles/base.scss +42 -0
  51. package/src/types/env.d.ts +24 -0
  52. package/src/types/go-captcha-uni.d.ts +7 -0
  53. package/src/types/uni-helper.d.ts +54 -0
  54. package/src/uni.scss +76 -0
  55. package/src/utils/auth.ts +67 -0
  56. package/src/utils/file.ts +95 -0
  57. package/src/utils/http.ts +430 -0
  58. package/src/utils/index.ts +79 -0
  59. package/src/utils/navigation.ts +81 -0
  60. package/src/utils/passwordCrypto.ts +275 -0
  61. package/src/views/pages/index/index.vue +227 -0
  62. package/src/views/pages/login/login.vue +1133 -0
  63. package/src/views/pages/login/protocal.vue +176 -0
  64. package/src/views/pages/status/index.vue +36 -0
  65. package/src/views/pages/webview/webview.vue +136 -0
  66. package/src/vite.ts +474 -0
package/src/vite.ts ADDED
@@ -0,0 +1,474 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ rmdirSync,
8
+ rmSync,
9
+ statSync,
10
+ unlinkSync,
11
+ writeFileSync,
12
+ } from 'node:fs'
13
+ import { createRequire } from 'node:module'
14
+ import { dirname, isAbsolute, relative, resolve } from 'node:path'
15
+ import uniPlugin from '@dcloudio/vite-plugin-uni'
16
+ import { defineConfig, loadEnv } from 'vite'
17
+ import type { Plugin, UserConfig } from 'vite'
18
+ import type { KratosAppModule, KratosAppPageConfig } from './module'
19
+
20
+ export { defineConfig, loadEnv }
21
+ export type { ConfigEnv, UserConfig } from 'vite'
22
+
23
+ type PageEntry = { path: string; style?: Record<string, unknown> }
24
+ type SubPackage = { root: string; pages: PageEntry[] }
25
+ type PagesManifest = {
26
+ pages: PageEntry[]
27
+ subPackages?: SubPackage[]
28
+ [key: string]: unknown
29
+ }
30
+ type ScannedPage = {
31
+ route: string
32
+ source: string
33
+ style?: Record<string, unknown>
34
+ }
35
+ let sourceVersionCounter = 0
36
+ type BuildTransaction = {
37
+ inputDir: string
38
+ pagesFile: string
39
+ originalPages: string
40
+ generatedFiles: string[]
41
+ generatedStaticFiles: string[]
42
+ generatedStatic?: boolean
43
+ ownerPid?: number
44
+ }
45
+
46
+ /** uni-app Vite 插件参数。 */
47
+ export interface KratosAppViteOptions {
48
+ /** 宿主模块清单,顺序同时决定覆盖优先级。 */
49
+ modules: KratosAppModule[]
50
+ }
51
+
52
+ /** 创建 uni-app 官方 Vite 插件。 */
53
+ export function createKratosUniPlugin() {
54
+ const createPlugin =
55
+ typeof uniPlugin === 'function'
56
+ ? uniPlugin
57
+ : (uniPlugin as unknown as { default: typeof uniPlugin }).default
58
+ return createPlugin()
59
+ }
60
+
61
+ /** 创建自动页面装配插件。 */
62
+ export function kratosApp(options: KratosAppViteOptions): Plugin {
63
+ const sourceVersion = `${process.pid}-${Date.now()}-${++sourceVersionCounter}`
64
+ const state: {
65
+ inputDir?: string
66
+ pagesFile?: string
67
+ originalPages?: string
68
+ generatedFiles: string[]
69
+ generatedStaticFiles: string[]
70
+ cleaned: boolean
71
+ persistent: boolean
72
+ transactionFile?: string
73
+ moduleRoots: Map<string, string>
74
+ } = {
75
+ generatedFiles: [],
76
+ generatedStaticFiles: [],
77
+ cleaned: false,
78
+ persistent: false,
79
+ moduleRoots: new Map(),
80
+ }
81
+
82
+ const cleanup = () => {
83
+ if (state.cleaned) return
84
+ state.cleaned = true
85
+ process.removeListener('exit', cleanup)
86
+ process.removeListener('SIGINT', handleSigint)
87
+ process.removeListener('SIGTERM', handleSigterm)
88
+ for (const file of [...state.generatedFiles].reverse()) {
89
+ if (existsSync(file)) unlinkSync(file)
90
+ removeEmptyParents(dirname(file), state.inputDir ?? '')
91
+ }
92
+ if (state.inputDir) {
93
+ removeGeneratedStaticFiles(state.generatedStaticFiles, resolve(state.inputDir, 'static'))
94
+ }
95
+ if (state.pagesFile && state.originalPages !== undefined) {
96
+ writeFileSync(state.pagesFile, state.originalPages)
97
+ }
98
+ if (state.transactionFile && existsSync(state.transactionFile)) {
99
+ unlinkSync(state.transactionFile)
100
+ }
101
+ }
102
+ const handleSigint = () => {
103
+ cleanup()
104
+ process.exit(130)
105
+ }
106
+ const handleSigterm = () => {
107
+ cleanup()
108
+ process.exit(143)
109
+ }
110
+
111
+ return {
112
+ name: 'kratos-uni-app-pages',
113
+ enforce: 'pre',
114
+ configResolved(config) {
115
+ state.persistent = config.command === 'serve' || Boolean(config.build.watch)
116
+ },
117
+ resolveId(source, importer) {
118
+ const [sourcePath, query = ''] = source.split('?', 2)
119
+ let target: string | undefined
120
+ if (
121
+ sourcePath === '@liujitcn/kratos-uni-app-core' ||
122
+ sourcePath.startsWith('@liujitcn/kratos-uni-app-core/')
123
+ ) {
124
+ const coreRoot = state.moduleRoots.get('@liujitcn/kratos-uni-app-core')
125
+ if (coreRoot) {
126
+ target = resolveWorkspacePackageSource(
127
+ coreRoot,
128
+ sourcePath.slice('@liujitcn/kratos-uni-app-core'.length).replace(/^\/+/, ''),
129
+ )
130
+ }
131
+ } else if (
132
+ sourcePath === '@liujitcn/kratos-uni-app-system' ||
133
+ sourcePath.startsWith('@liujitcn/kratos-uni-app-system/')
134
+ ) {
135
+ const systemRoot = state.moduleRoots.get('@liujitcn/kratos-uni-app-system')
136
+ if (systemRoot) {
137
+ target = resolveWorkspacePackageSource(
138
+ systemRoot,
139
+ sourcePath.slice('@liujitcn/kratos-uni-app-system'.length).replace(/^\/+/, ''),
140
+ )
141
+ }
142
+ } else if (sourcePath.startsWith('@kratos-uni-app-system-source/')) {
143
+ const systemRoot = state.moduleRoots.get('@liujitcn/kratos-uni-app-system')
144
+ if (systemRoot) {
145
+ target = resolve(
146
+ systemRoot,
147
+ 'src',
148
+ sourcePath.slice('@kratos-uni-app-system-source/'.length),
149
+ )
150
+ }
151
+ } else if (sourcePath.startsWith('@kratos-uni-app-core-source/')) {
152
+ const coreRoot = state.moduleRoots.get('@liujitcn/kratos-uni-app-core')
153
+ if (coreRoot) {
154
+ target = resolve(coreRoot, 'src', sourcePath.slice('@kratos-uni-app-core-source/'.length))
155
+ }
156
+ } else if (sourcePath.startsWith('.') && importer) {
157
+ const importerPath = importer.split('?', 1)[0]
158
+ const moduleRoot = [...state.moduleRoots.values()].find((root) =>
159
+ isWithinRoot(resolve(root, 'src'), importerPath),
160
+ )
161
+ if (moduleRoot) target = resolve(dirname(importerPath), sourcePath)
162
+ }
163
+ if (!target) return
164
+ const resolved = resolveSourceFile(target)
165
+ const importQuery = query || dependencyVersionQuery(importer, sourceVersion)
166
+ return `${resolved}${importQuery ? `?${importQuery}` : ''}`
167
+ },
168
+ transform(code, id) {
169
+ const sourceFile = id.split('?', 1)[0]
170
+ const isModuleSource = [...state.moduleRoots.values()].some((root) =>
171
+ isWithinRoot(resolve(root, 'src'), sourceFile),
172
+ )
173
+ const isLinkedModuleSource =
174
+ sourceFile.includes('/@liujitcn/kratos-uni-app-core/') ||
175
+ sourceFile.includes('/@liujitcn/kratos-uni-app-system/')
176
+ if (!isModuleSource && !isLinkedModuleSource) return
177
+ const transformed = code
178
+ .replace(/@system\//g, '@kratos-uni-app-system-source/')
179
+ .replace(/@\//g, '@kratos-uni-app-core-source/')
180
+ if (transformed === code) return
181
+ return { code: transformed, map: null }
182
+ },
183
+ config(): UserConfig {
184
+ const inputDir = process.env.UNI_INPUT_DIR || resolve(process.cwd(), 'src')
185
+ const pagesFile = resolve(inputDir, 'pages.json')
186
+ const transactionFile = resolve(inputDir, '../.kratos-uni-app-pages-state.json')
187
+ recoverBuildTransaction(transactionFile, inputDir, pagesFile)
188
+ const hostRequire = createRequire(resolve(inputDir, '../package.json'))
189
+ const originalPages = readFileSync(pagesFile, 'utf8')
190
+ const manifest = JSON.parse(originalPages) as PagesManifest
191
+ manifest.pages ??= []
192
+ const pageMap = new Map<string, ScannedPage>()
193
+ const moduleRoots = options.modules.map((module) => {
194
+ const linkedRoot = resolve(inputDir, '../node_modules', ...module.name.split('/'))
195
+ return {
196
+ module,
197
+ root: existsSync(resolve(linkedRoot, 'package.json'))
198
+ ? linkedRoot
199
+ : dirname(hostRequire.resolve(`${module.name}/package.json`)),
200
+ }
201
+ })
202
+ state.moduleRoots = new Map(moduleRoots.map(({ module, root }) => [module.name, root]))
203
+
204
+ moduleRoots.forEach(({ module, root }) => {
205
+ scanViews(resolve(root, 'src/views')).forEach((page) => {
206
+ const config = module.pages[page.route] ?? {}
207
+ const route = normalizeRoute(config.route ?? page.route)
208
+ pageMap.set(route, {
209
+ route,
210
+ source: `${module.name}/views/${page.route}.vue`,
211
+ style: config.style,
212
+ })
213
+ })
214
+ })
215
+
216
+ state.inputDir = inputDir
217
+ state.pagesFile = pagesFile
218
+ state.originalPages = originalPages
219
+ state.transactionFile = transactionFile
220
+ state.generatedFiles = [...pageMap.values()]
221
+ .map((page) => resolve(inputDir, `${page.route}.vue`))
222
+ .filter((target) => !existsSync(target))
223
+ const staticTarget = resolve(inputDir, 'static')
224
+ const staticFiles = collectModuleStaticFiles(
225
+ moduleRoots.map(({ root }) => resolve(root, 'src/static')),
226
+ staticTarget,
227
+ )
228
+ state.generatedStaticFiles = staticFiles.map(({ target }) => target)
229
+ writeFileSync(
230
+ transactionFile,
231
+ `${JSON.stringify(
232
+ {
233
+ inputDir,
234
+ pagesFile,
235
+ originalPages,
236
+ generatedFiles: state.generatedFiles,
237
+ generatedStaticFiles: state.generatedStaticFiles,
238
+ ownerPid: process.pid,
239
+ } satisfies BuildTransaction,
240
+ null,
241
+ 2,
242
+ )}\n`,
243
+ { flag: 'wx' },
244
+ )
245
+ pageMap.forEach((page) => {
246
+ const target = resolve(inputDir, `${page.route}.vue`)
247
+ if (existsSync(target)) return
248
+ mkdirSync(dirname(target), { recursive: true })
249
+ writeFileSync(target, createPageWrapper(page))
250
+ appendPage(manifest, page)
251
+ })
252
+
253
+ staticFiles.forEach(({ source, target }) => {
254
+ mkdirSync(dirname(target), { recursive: true })
255
+ copyFileSync(source, target)
256
+ })
257
+ writeFileSync(pagesFile, `${JSON.stringify(manifest, null, 2)}\n`)
258
+ process.once('exit', cleanup)
259
+ process.prependOnceListener('SIGINT', handleSigint)
260
+ process.prependOnceListener('SIGTERM', handleSigterm)
261
+ return {
262
+ resolve: {
263
+ alias: [
264
+ {
265
+ find: '@kratos-uni-app-core-source',
266
+ replacement: resolve(
267
+ state.moduleRoots.get('@liujitcn/kratos-uni-app-core') ?? '',
268
+ 'src',
269
+ ),
270
+ },
271
+ {
272
+ find: '@kratos-uni-app-system-source',
273
+ replacement: resolve(
274
+ state.moduleRoots.get('@liujitcn/kratos-uni-app-system') ?? '',
275
+ 'src',
276
+ ),
277
+ },
278
+ ],
279
+ },
280
+ optimizeDeps: {
281
+ exclude: options.modules.map((module) => module.name),
282
+ },
283
+ }
284
+ },
285
+ configureServer(server) {
286
+ server.httpServer?.once('close', cleanup)
287
+ },
288
+ closeBundle() {
289
+ if (!state.persistent) cleanup()
290
+ },
291
+ closeWatcher: cleanup,
292
+ }
293
+ }
294
+
295
+ function recoverBuildTransaction(
296
+ transactionFile: string,
297
+ inputDir: string,
298
+ pagesFile: string,
299
+ ): void {
300
+ if (!existsSync(transactionFile)) return
301
+ const transaction = JSON.parse(readFileSync(transactionFile, 'utf8')) as BuildTransaction
302
+ if (transaction.inputDir !== inputDir || transaction.pagesFile !== pagesFile) {
303
+ throw new Error(`uni-app 构建事务目录不匹配:${transactionFile}`)
304
+ }
305
+ if (
306
+ transaction.ownerPid !== undefined &&
307
+ transaction.ownerPid !== process.pid &&
308
+ isProcessRunning(transaction.ownerPid)
309
+ ) {
310
+ throw new Error(
311
+ `页面装配已由进程 ${transaction.ownerPid} 使用,请先停止另一个 H5 或小程序开发进程`,
312
+ )
313
+ }
314
+ for (const file of [...transaction.generatedFiles].reverse()) {
315
+ if (existsSync(file)) unlinkSync(file)
316
+ removeEmptyParents(dirname(file), inputDir)
317
+ }
318
+ removeGeneratedStaticFiles(transaction.generatedStaticFiles ?? [], resolve(inputDir, 'static'))
319
+ // 兼容旧事务:旧格式仅在 static 原本不存在时记录整个目录由插件生成。
320
+ if (transaction.generatedStatic && transaction.generatedStaticFiles === undefined) {
321
+ rmSync(resolve(inputDir, 'static'), { recursive: true, force: true })
322
+ }
323
+ writeFileSync(pagesFile, transaction.originalPages)
324
+ unlinkSync(transactionFile)
325
+ }
326
+
327
+ // isProcessRunning 判断页面事务的所属进程是否仍然存活。
328
+ function isProcessRunning(pid: number): boolean {
329
+ try {
330
+ process.kill(pid, 0)
331
+ return true
332
+ } catch (error) {
333
+ return (error as NodeJS.ErrnoException).code === 'EPERM'
334
+ }
335
+ }
336
+
337
+ function collectModuleStaticFiles(
338
+ sourceRoots: string[],
339
+ targetRoot: string,
340
+ ): Array<{ source: string; target: string }> {
341
+ const fileMap = new Map<string, { source: string; target: string }>()
342
+ sourceRoots.forEach((sourceRoot) => {
343
+ scanFiles(sourceRoot).forEach((source) => {
344
+ const target = resolve(targetRoot, relative(sourceRoot, source))
345
+ if (!existsSync(target)) fileMap.set(target, { source, target })
346
+ })
347
+ })
348
+ return [...fileMap.values()]
349
+ }
350
+
351
+ function scanFiles(root: string): string[] {
352
+ if (!existsSync(root)) return []
353
+ const files: string[] = []
354
+ const visit = (directory: string) => {
355
+ readdirSync(directory).forEach((entry) => {
356
+ const path = resolve(directory, entry)
357
+ if (statSync(path).isDirectory()) visit(path)
358
+ else files.push(path)
359
+ })
360
+ }
361
+ visit(root)
362
+ return files
363
+ }
364
+
365
+ function removeGeneratedStaticFiles(files: string[], staticRoot: string): void {
366
+ for (const file of [...files].reverse()) {
367
+ if (existsSync(file)) unlinkSync(file)
368
+ removeEmptyParents(dirname(file), staticRoot)
369
+ }
370
+ }
371
+
372
+ function scanViews(root: string): Array<{ route: string; source: string }> {
373
+ if (!existsSync(root)) return []
374
+ const files: string[] = []
375
+ const visit = (directory: string) => {
376
+ readdirSync(directory).forEach((entry) => {
377
+ const path = resolve(directory, entry)
378
+ if (statSync(path).isDirectory()) {
379
+ if (entry !== 'components') visit(path)
380
+ } else if (entry.endsWith('.vue')) {
381
+ files.push(path)
382
+ }
383
+ })
384
+ }
385
+ visit(root)
386
+ return files.map((source) => ({
387
+ route: normalizeRoute(relative(root, source)),
388
+ source,
389
+ }))
390
+ }
391
+
392
+ function appendPage(manifest: PagesManifest, page: ScannedPage): void {
393
+ const [first, ...rest] = page.route.split('/')
394
+ if (!first.startsWith('pages') || first === 'pages') {
395
+ manifest.pages.push({ path: page.route, style: page.style })
396
+ return
397
+ }
398
+ manifest.subPackages ??= []
399
+ let subPackage = manifest.subPackages.find((item) => item.root === first)
400
+ if (!subPackage) {
401
+ subPackage = { root: first, pages: [] }
402
+ manifest.subPackages.push(subPackage)
403
+ }
404
+ subPackage.pages.push({ path: rest.join('/'), style: page.style })
405
+ }
406
+
407
+ function createPageWrapper(page: ScannedPage): string {
408
+ const source = page.source.replace(/\\/g, '/')
409
+ return `<script setup lang="ts">
410
+ import KratosPage from ${JSON.stringify(source)}
411
+ import KratosTabBar from '@liujitcn/kratos-uni-app-core/components/KratosTabBar.vue'
412
+
413
+ defineOptions({ inheritAttrs: false })
414
+ </script>
415
+
416
+ <template>
417
+ <KratosPage />
418
+ <KratosTabBar route=${JSON.stringify(page.route)} />
419
+ </template>
420
+ `
421
+ }
422
+
423
+ function normalizeRoute(route: string): string {
424
+ return route
425
+ .replace(/\\/g, '/')
426
+ .replace(/^\/+/, '')
427
+ .replace(/\.vue$/, '')
428
+ }
429
+
430
+ function removeEmptyParents(directory: string, boundary: string): void {
431
+ let current = directory
432
+ while (current !== boundary && current.startsWith(boundary) && existsSync(current)) {
433
+ if (readdirSync(current).length) return
434
+ rmdirSync(current)
435
+ current = dirname(current)
436
+ }
437
+ }
438
+
439
+ function resolveSourceFile(target: string): string {
440
+ const candidates = [
441
+ target,
442
+ `${target}.ts`,
443
+ `${target}.js`,
444
+ `${target}.vue`,
445
+ resolve(target, 'index.ts'),
446
+ resolve(target, 'index.js'),
447
+ ]
448
+ const resolved =
449
+ candidates.find((candidate) => existsSync(candidate) && statSync(candidate).isFile()) ?? target
450
+ if (!isAbsolute(resolved)) throw new Error(`模块源码路径不是绝对路径:${resolved}`)
451
+ return resolved
452
+ }
453
+
454
+ // dependencyVersionQuery 继承依赖版本并隔离开发服务缓存,避免源码重复实例或跨服务过期。
455
+ function dependencyVersionQuery(importer: string | undefined, sourceVersion: string): string {
456
+ const query = importer?.split('?', 2)[1] ?? ''
457
+ const version = new URLSearchParams(query).get('v')
458
+ const params = new URLSearchParams()
459
+ if (version) params.set('v', version)
460
+ params.set('kratos', sourceVersion)
461
+ return params.toString()
462
+ }
463
+
464
+ function resolveWorkspacePackageSource(packageRoot: string, subpath: string): string {
465
+ if (!subpath) return resolve(packageRoot, 'src/index.ts')
466
+ if (subpath === 'module') return resolve(packageRoot, 'src/module.ts')
467
+ if (subpath === 'stores') return resolve(packageRoot, 'src/stores/index.ts')
468
+ return resolve(packageRoot, 'src', subpath)
469
+ }
470
+
471
+ function isWithinRoot(root: string, target: string): boolean {
472
+ const relativePath = relative(root, target)
473
+ return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath))
474
+ }