@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
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @liujitcn/kratos-taro-app-core
|
|
2
|
+
|
|
3
|
+
`@liujitcn/kratos-taro-app-core` 是 Taro 应用底座,负责模块协议、启动流程、认证请求、共享状态、动态导航、基础页面、公共静态资源和构建期页面装配。core 不依赖 UI 或业务模块。
|
|
4
|
+
|
|
5
|
+
## 主要能力
|
|
6
|
+
|
|
7
|
+
- 账号密码、行为验证码、微信登录、OAuth、Token 刷新和退出。
|
|
8
|
+
- 统一 HTTP、上传、下载、文件预览与 RSA/AES 密码加密。
|
|
9
|
+
- Zustand 用户、配置和导航状态。
|
|
10
|
+
- 接口驱动的逻辑路由、访问模式、自绘 tabBar 和稳定 `viewKey`。
|
|
11
|
+
- 打包静态资源公共路径解析,以及 H5 默认导航栏兼容框架。
|
|
12
|
+
- 首页、登录、协议、WebView、启动/错误状态页面。
|
|
13
|
+
- H5 与微信小程序共用的模块页面装配 runner。
|
|
14
|
+
|
|
15
|
+
## 公开入口
|
|
16
|
+
|
|
17
|
+
- `@liujitcn/kratos-taro-app-core`:启动、模块、导航、状态和常用工具,包含 `resolveBundledAsset()`。
|
|
18
|
+
- `@liujitcn/kratos-taro-app-core/build`:构建期模块描述。
|
|
19
|
+
- `@liujitcn/kratos-taro-app-core/runner`:Taro 构建装配入口。
|
|
20
|
+
- `api/*`、`components/*`、`rpc/*`、`styles/*`、`utils/*`、`views/*`:白名单子路径。
|
|
21
|
+
|
|
22
|
+
业务模块只能通过这些 exports 使用底座,不得相对引用 core 源码。RPC 由 `make -C backend ts-taro-app` 生成,不能手工编辑。
|
|
23
|
+
|
|
24
|
+
## Runner 协议
|
|
25
|
+
|
|
26
|
+
模块必须同时提供运行时定义和 `./build` 导出。构建入口返回模块名称、包根目录与页面清单;runner 根据宿主的静态 `module-manifest.ts` 解析模块顺序,扫描页面并创建事务。
|
|
27
|
+
|
|
28
|
+
runner 支持命名导入和默认导入的模块,后注册模块可以覆盖相同物理路由。页面私有 `components` 不会成为路由,模块 static 会先合并到宿主再由 Taro 编译。H5 下,runner 根据页面 `navigationStyle` 和 `navigationBar*` 配置生成默认顶部导航栏;微信小程序继续使用原生导航栏。构建失败、正常退出和收到终止信号时都会执行恢复。
|
|
29
|
+
|
|
30
|
+
## 验证
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm --filter @liujitcn/kratos-taro-app-core tsc
|
|
34
|
+
pnpm test
|
|
35
|
+
pnpm check:exports
|
|
36
|
+
```
|
package/dist/build.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/build.ts
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
// src/pages.ts
|
|
6
|
+
var corePages = {
|
|
7
|
+
"pages/index/index": {
|
|
8
|
+
style: {
|
|
9
|
+
navigationStyle: "custom",
|
|
10
|
+
navigationBarTextStyle: "white",
|
|
11
|
+
navigationBarTitleText: "\u9996\u9875"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"pages/login/login": { style: { navigationBarTitleText: "\u767B\u5F55" } },
|
|
15
|
+
"pages/login/protocal": { style: { navigationBarTitleText: "\u534F\u8BAE\u8BE6\u60C5" } },
|
|
16
|
+
"pages/webview/webview": { style: { navigationBarTitleText: "" } },
|
|
17
|
+
"pages/status/index": {
|
|
18
|
+
style: {
|
|
19
|
+
navigationStyle: "custom",
|
|
20
|
+
navigationBarTitleText: ""
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/build.ts
|
|
26
|
+
function defineKratosTaroBuildModule(module) {
|
|
27
|
+
return module;
|
|
28
|
+
}
|
|
29
|
+
var packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
30
|
+
var buildModule = defineKratosTaroBuildModule({
|
|
31
|
+
name: "@liujitcn/kratos-taro-app-core",
|
|
32
|
+
root: packageRoot,
|
|
33
|
+
pages: corePages
|
|
34
|
+
});
|
|
35
|
+
export {
|
|
36
|
+
buildModule,
|
|
37
|
+
defineKratosTaroBuildModule
|
|
38
|
+
};
|
package/dist/runner.mjs
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// src/runner.ts
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
rmdirSync,
|
|
9
|
+
statSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
writeFileSync
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { dirname, relative, resolve } from "node:path";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import ts from "typescript";
|
|
18
|
+
async function main() {
|
|
19
|
+
const options = parseArguments(process.argv.slice(2));
|
|
20
|
+
const hostRoot = process.cwd();
|
|
21
|
+
const inputDir = resolve(hostRoot, "src");
|
|
22
|
+
const appConfigFile = resolve(inputDir, "app.config.ts");
|
|
23
|
+
const baseConfigFile = resolve(inputDir, "app.config.base.json");
|
|
24
|
+
const transactionFile = resolve(hostRoot, ".kratos-taro-app-pages-state.json");
|
|
25
|
+
recoverBuildTransaction(transactionFile, inputDir, appConfigFile);
|
|
26
|
+
const originalAppConfig = readFileSync(appConfigFile, "utf8");
|
|
27
|
+
const manifest = JSON.parse(readFileSync(baseConfigFile, "utf8"));
|
|
28
|
+
const modules = await loadBuildModules(hostRoot, resolve(inputDir, "module-manifest.ts"));
|
|
29
|
+
const pageMap = /* @__PURE__ */ new Map();
|
|
30
|
+
modules.forEach((module) => {
|
|
31
|
+
scanViews(resolve(module.root, "src/views")).forEach((page) => {
|
|
32
|
+
const config = module.pages[page.route] ?? {};
|
|
33
|
+
const route = normalizeRoute(config.route ?? page.route);
|
|
34
|
+
pageMap.set(route, {
|
|
35
|
+
route,
|
|
36
|
+
source: `${module.name}/views/${page.route}`,
|
|
37
|
+
style: config.style
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
const generatedFiles = [...pageMap.values()].flatMap((page) => {
|
|
42
|
+
const pageFile = resolve(inputDir, `${page.route}.tsx`);
|
|
43
|
+
const configFile = resolve(inputDir, `${page.route}.config.ts`);
|
|
44
|
+
return [pageFile, configFile].filter((file) => !existsSync(file));
|
|
45
|
+
});
|
|
46
|
+
const staticRoot = resolve(inputDir, "static");
|
|
47
|
+
const staticFiles = collectModuleStaticFiles(
|
|
48
|
+
modules.map((module) => resolve(module.root, "src/static")),
|
|
49
|
+
staticRoot
|
|
50
|
+
);
|
|
51
|
+
const generatedStaticFiles = staticFiles.map(({ target }) => target);
|
|
52
|
+
const transaction = {
|
|
53
|
+
inputDir,
|
|
54
|
+
appConfigFile,
|
|
55
|
+
originalAppConfig,
|
|
56
|
+
generatedFiles,
|
|
57
|
+
generatedStaticFiles,
|
|
58
|
+
ownerPid: process.pid
|
|
59
|
+
};
|
|
60
|
+
writeFileSync(transactionFile, `${JSON.stringify(transaction, null, 2)}
|
|
61
|
+
`, { flag: "wx" });
|
|
62
|
+
let cleaned = false;
|
|
63
|
+
const cleanup = () => {
|
|
64
|
+
if (cleaned) return;
|
|
65
|
+
cleaned = true;
|
|
66
|
+
cleanupTransaction(transaction);
|
|
67
|
+
if (existsSync(transactionFile)) unlinkSync(transactionFile);
|
|
68
|
+
};
|
|
69
|
+
const handleSignal = (signal) => {
|
|
70
|
+
child?.kill(signal);
|
|
71
|
+
};
|
|
72
|
+
let child;
|
|
73
|
+
process.once("SIGINT", () => handleSignal("SIGINT"));
|
|
74
|
+
process.once("SIGTERM", () => handleSignal("SIGTERM"));
|
|
75
|
+
try {
|
|
76
|
+
pageMap.forEach((page) => {
|
|
77
|
+
appendPage(manifest, page.route);
|
|
78
|
+
const pageFile = resolve(inputDir, `${page.route}.tsx`);
|
|
79
|
+
const configFile = resolve(inputDir, `${page.route}.config.ts`);
|
|
80
|
+
if (!existsSync(pageFile)) {
|
|
81
|
+
mkdirSync(dirname(pageFile), { recursive: true });
|
|
82
|
+
writeFileSync(pageFile, createPageWrapper(page));
|
|
83
|
+
}
|
|
84
|
+
if (!existsSync(configFile)) {
|
|
85
|
+
writeFileSync(configFile, createPageConfig(page));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
staticFiles.forEach(({ source, target }) => {
|
|
89
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
90
|
+
copyFileSync(source, target);
|
|
91
|
+
});
|
|
92
|
+
writeFileSync(
|
|
93
|
+
appConfigFile,
|
|
94
|
+
`export default defineAppConfig(${JSON.stringify(manifest, null, 2)})
|
|
95
|
+
`
|
|
96
|
+
);
|
|
97
|
+
const args = ["exec", "taro", "build", "--type", options.type, "--mode", options.mode];
|
|
98
|
+
if (options.watch) args.push("--watch");
|
|
99
|
+
child = spawn(process.platform === "win32" ? "pnpm.cmd" : "pnpm", args, {
|
|
100
|
+
cwd: hostRoot,
|
|
101
|
+
env: {
|
|
102
|
+
...process.env,
|
|
103
|
+
NODE_ENV: options.mode === "development" ? "development" : "production"
|
|
104
|
+
},
|
|
105
|
+
stdio: "inherit"
|
|
106
|
+
});
|
|
107
|
+
const exitCode = await new Promise((complete, reject) => {
|
|
108
|
+
child?.once("error", reject);
|
|
109
|
+
child?.once("exit", (code, signal) => {
|
|
110
|
+
if (signal) {
|
|
111
|
+
complete(signal === "SIGINT" ? 130 : 143);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
complete(code ?? 1);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
118
|
+
} finally {
|
|
119
|
+
cleanup();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function parseArguments(args) {
|
|
123
|
+
let type;
|
|
124
|
+
let mode = "production";
|
|
125
|
+
let watch = false;
|
|
126
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
127
|
+
const argument = args[index];
|
|
128
|
+
if (argument === "--type") {
|
|
129
|
+
const value = args[++index];
|
|
130
|
+
if (value !== "h5" && value !== "weapp") throw new Error(`\u4E0D\u652F\u6301\u7684 Taro \u7C7B\u578B\uFF1A${value}`);
|
|
131
|
+
type = value;
|
|
132
|
+
} else if (argument === "--mode") {
|
|
133
|
+
mode = args[++index] || mode;
|
|
134
|
+
} else if (argument === "--watch") {
|
|
135
|
+
watch = true;
|
|
136
|
+
} else {
|
|
137
|
+
throw new Error(`\u672A\u77E5\u53C2\u6570\uFF1A${argument}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!type) throw new Error("\u7F3A\u5C11 --type h5|weapp");
|
|
141
|
+
return { type, mode, watch };
|
|
142
|
+
}
|
|
143
|
+
async function loadBuildModules(hostRoot, manifestFile) {
|
|
144
|
+
const source = readFileSync(manifestFile, "utf8");
|
|
145
|
+
const sourceFile = ts.createSourceFile(
|
|
146
|
+
manifestFile,
|
|
147
|
+
source,
|
|
148
|
+
ts.ScriptTarget.Latest,
|
|
149
|
+
true,
|
|
150
|
+
ts.ScriptKind.TS
|
|
151
|
+
);
|
|
152
|
+
const imports = /* @__PURE__ */ new Map();
|
|
153
|
+
let moduleNames = [];
|
|
154
|
+
sourceFile.forEachChild((node) => {
|
|
155
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
156
|
+
const importClause = node.importClause;
|
|
157
|
+
const bindings = node.importClause?.namedBindings;
|
|
158
|
+
const packageName = node.moduleSpecifier.text;
|
|
159
|
+
if (importClause?.name) imports.set(importClause.name.text, packageName);
|
|
160
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
161
|
+
bindings.elements.forEach((element) => {
|
|
162
|
+
imports.set(element.name.text, packageName);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!ts.isVariableStatement(node)) return;
|
|
168
|
+
node.declarationList.declarations.forEach((declaration) => {
|
|
169
|
+
if (ts.isIdentifier(declaration.name) && declaration.name.text === "moduleManifest" && declaration.initializer && ts.isArrayLiteralExpression(declaration.initializer)) {
|
|
170
|
+
moduleNames = declaration.initializer.elements.filter(ts.isIdentifier).map((element) => element.text);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
if (!moduleNames.length) throw new Error(`\u6A21\u5757\u6E05\u5355\u672A\u5BFC\u51FA moduleManifest\uFF1A${manifestFile}`);
|
|
175
|
+
const hostRequire = createRequire(resolve(hostRoot, "package.json"));
|
|
176
|
+
return Promise.all(
|
|
177
|
+
moduleNames.map(async (name) => {
|
|
178
|
+
const packageName = imports.get(name);
|
|
179
|
+
if (!packageName) throw new Error(`\u6A21\u5757 ${name} \u7F3A\u5C11\u9759\u6001 import`);
|
|
180
|
+
const buildEntry = hostRequire.resolve(`${packageName}/build`);
|
|
181
|
+
const loaded = await import(pathToFileURL(buildEntry).href);
|
|
182
|
+
if (!loaded.buildModule) throw new Error(`${packageName}/build \u672A\u5BFC\u51FA buildModule`);
|
|
183
|
+
return loaded.buildModule;
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function recoverBuildTransaction(transactionFile, inputDir, appConfigFile) {
|
|
188
|
+
if (!existsSync(transactionFile)) return;
|
|
189
|
+
const transaction = JSON.parse(readFileSync(transactionFile, "utf8"));
|
|
190
|
+
if (transaction.inputDir !== inputDir || transaction.appConfigFile !== appConfigFile) {
|
|
191
|
+
throw new Error(`Taro \u6784\u5EFA\u4E8B\u52A1\u76EE\u5F55\u4E0D\u5339\u914D\uFF1A${transactionFile}`);
|
|
192
|
+
}
|
|
193
|
+
if (transaction.ownerPid !== process.pid && isProcessRunning(transaction.ownerPid)) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`\u9875\u9762\u88C5\u914D\u5DF2\u7531\u8FDB\u7A0B ${transaction.ownerPid} \u4F7F\u7528\uFF0C\u8BF7\u5148\u505C\u6B62\u53E6\u4E00\u4E2A H5 \u6216\u5C0F\u7A0B\u5E8F\u5F00\u53D1\u8FDB\u7A0B`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
cleanupTransaction(transaction);
|
|
199
|
+
unlinkSync(transactionFile);
|
|
200
|
+
}
|
|
201
|
+
function cleanupTransaction(transaction) {
|
|
202
|
+
for (const file of [...transaction.generatedFiles].reverse()) {
|
|
203
|
+
if (existsSync(file)) unlinkSync(file);
|
|
204
|
+
removeEmptyParents(dirname(file), transaction.inputDir);
|
|
205
|
+
}
|
|
206
|
+
removeGeneratedStaticFiles(transaction.generatedStaticFiles, resolve(transaction.inputDir, "static"));
|
|
207
|
+
writeFileSync(transaction.appConfigFile, transaction.originalAppConfig);
|
|
208
|
+
}
|
|
209
|
+
function isProcessRunning(pid) {
|
|
210
|
+
try {
|
|
211
|
+
process.kill(pid, 0);
|
|
212
|
+
return true;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return error.code === "EPERM";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function scanViews(root) {
|
|
218
|
+
if (!existsSync(root)) return [];
|
|
219
|
+
const files = [];
|
|
220
|
+
const visit = (directory) => {
|
|
221
|
+
readdirSync(directory).forEach((entry) => {
|
|
222
|
+
const path = resolve(directory, entry);
|
|
223
|
+
if (statSync(path).isDirectory()) {
|
|
224
|
+
if (entry !== "components") visit(path);
|
|
225
|
+
} else if (entry.endsWith(".tsx")) {
|
|
226
|
+
files.push(path);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
visit(root);
|
|
231
|
+
return files.map((source) => ({
|
|
232
|
+
route: normalizeRoute(relative(root, source)),
|
|
233
|
+
source
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
function collectModuleStaticFiles(sourceRoots, targetRoot) {
|
|
237
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
238
|
+
sourceRoots.forEach((sourceRoot) => {
|
|
239
|
+
scanFiles(sourceRoot).forEach((source) => {
|
|
240
|
+
const target = resolve(targetRoot, relative(sourceRoot, source));
|
|
241
|
+
if (!existsSync(target)) fileMap.set(target, { source, target });
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
return [...fileMap.values()];
|
|
245
|
+
}
|
|
246
|
+
function scanFiles(root) {
|
|
247
|
+
if (!existsSync(root)) return [];
|
|
248
|
+
const files = [];
|
|
249
|
+
const visit = (directory) => {
|
|
250
|
+
readdirSync(directory).forEach((entry) => {
|
|
251
|
+
const path = resolve(directory, entry);
|
|
252
|
+
if (statSync(path).isDirectory()) visit(path);
|
|
253
|
+
else files.push(path);
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
visit(root);
|
|
257
|
+
return files;
|
|
258
|
+
}
|
|
259
|
+
function appendPage(manifest, route) {
|
|
260
|
+
const [first, ...rest] = route.split("/");
|
|
261
|
+
if (!first.startsWith("pages") || first === "pages") {
|
|
262
|
+
if (!manifest.pages.includes(route)) manifest.pages.push(route);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
manifest.subpackages ??= [];
|
|
266
|
+
let subpackage = manifest.subpackages.find((item) => item.root === first);
|
|
267
|
+
if (!subpackage) {
|
|
268
|
+
subpackage = { root: first, pages: [] };
|
|
269
|
+
manifest.subpackages.push(subpackage);
|
|
270
|
+
}
|
|
271
|
+
const subRoute = rest.join("/");
|
|
272
|
+
if (!subpackage.pages.includes(subRoute)) subpackage.pages.push(subRoute);
|
|
273
|
+
}
|
|
274
|
+
function createPageWrapper(page) {
|
|
275
|
+
return `import KratosPage from ${JSON.stringify(page.source)}
|
|
276
|
+
import { KratosPageFrame } from '@liujitcn/kratos-taro-app-core/components/KratosPageFrame'
|
|
277
|
+
import { KratosTabBar } from '@liujitcn/kratos-taro-app-core/components/KratosTabBar'
|
|
278
|
+
|
|
279
|
+
/** \u81EA\u52A8\u751F\u6210\u7684\u6A21\u5757\u9875\u9762\u5305\u88C5\u5668\u3002 */
|
|
280
|
+
export default function KratosPageWrapper() {
|
|
281
|
+
return (
|
|
282
|
+
<>
|
|
283
|
+
<KratosPageFrame
|
|
284
|
+
navigationStyle=${JSON.stringify(page.style?.navigationStyle ?? "default")}
|
|
285
|
+
navigationBarTitleText=${JSON.stringify(page.style?.navigationBarTitleText ?? "")}
|
|
286
|
+
navigationBarBackgroundColor=${JSON.stringify(page.style?.navigationBarBackgroundColor ?? "#f8f8f8")}
|
|
287
|
+
navigationBarTextStyle=${JSON.stringify(page.style?.navigationBarTextStyle ?? "black")}
|
|
288
|
+
>
|
|
289
|
+
<KratosPage />
|
|
290
|
+
</KratosPageFrame>
|
|
291
|
+
<KratosTabBar route=${JSON.stringify(page.route)} />
|
|
292
|
+
</>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
function createPageConfig(page) {
|
|
298
|
+
return `export default definePageConfig(${JSON.stringify(page.style ?? {}, null, 2)})
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
function normalizeRoute(route) {
|
|
302
|
+
return route.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.tsx$/, "");
|
|
303
|
+
}
|
|
304
|
+
function removeGeneratedStaticFiles(files, staticRoot) {
|
|
305
|
+
for (const file of [...files].reverse()) {
|
|
306
|
+
if (existsSync(file)) unlinkSync(file);
|
|
307
|
+
removeEmptyParents(dirname(file), staticRoot);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function removeEmptyParents(directory, boundary) {
|
|
311
|
+
let current = directory;
|
|
312
|
+
while (current !== boundary && current.startsWith(boundary) && existsSync(current)) {
|
|
313
|
+
if (readdirSync(current).length) return;
|
|
314
|
+
rmdirSync(current);
|
|
315
|
+
current = dirname(current);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
void main().catch((error) => {
|
|
319
|
+
console.error(error);
|
|
320
|
+
process.exitCode = 1;
|
|
321
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liujitcn/kratos-taro-app-core",
|
|
3
|
+
"description": "Runtime, navigation, authentication, and build foundation for Kratos Taro apps.",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.ts",
|
|
13
|
+
"./build": {
|
|
14
|
+
"types": "./src/build.ts",
|
|
15
|
+
"import": "./dist/build.mjs",
|
|
16
|
+
"default": "./dist/build.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./runner": "./dist/runner.mjs",
|
|
19
|
+
"./api/*": "./src/api/*.ts",
|
|
20
|
+
"./components/*": "./src/components/*.tsx",
|
|
21
|
+
"./navigation": "./src/navigation.ts",
|
|
22
|
+
"./module": "./src/module.ts",
|
|
23
|
+
"./rpc/*": "./src/rpc/*.ts",
|
|
24
|
+
"./stores": "./src/stores/index.ts",
|
|
25
|
+
"./static/*": "./src/static/*",
|
|
26
|
+
"./styles/*": "./src/styles/*",
|
|
27
|
+
"./utils/*": "./src/utils/*.ts",
|
|
28
|
+
"./views/*": "./src/views/*",
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@tarojs/components": "4.2.1",
|
|
36
|
+
"@tarojs/taro": "4.2.1",
|
|
37
|
+
"asmcrypto.js": "2.3.2",
|
|
38
|
+
"go-captcha-react": "1.0.5",
|
|
39
|
+
"react": "18.3.1",
|
|
40
|
+
"typescript": "5.8.3",
|
|
41
|
+
"zustand": "5.0.6"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/react": "18.3.23"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "pnpm run build:entries && pnpm pack --pack-destination ../../dist/npm",
|
|
48
|
+
"build:entries": "esbuild src/build.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/build.mjs && esbuild src/runner.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/runner.mjs",
|
|
49
|
+
"tsc": "tsc --noEmit -p tsconfig.json"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { http } from '../../utils/http'
|
|
2
|
+
import type { ConfigService, GetConfigRequest, GetConfigResponse } from '../../rpc/base/v1/config'
|
|
3
|
+
|
|
4
|
+
const CONFIG_URL = '/v1/base/config'
|
|
5
|
+
|
|
6
|
+
/** 系统配置公共服务 */
|
|
7
|
+
export class ConfigServiceImpl implements ConfigService {
|
|
8
|
+
/** 获取系统配置 */
|
|
9
|
+
GetConfig(request: GetConfigRequest): Promise<GetConfigResponse> {
|
|
10
|
+
return http<GetConfigResponse>({
|
|
11
|
+
url: `${CONFIG_URL}`,
|
|
12
|
+
method: 'GET',
|
|
13
|
+
authMode: 'none',
|
|
14
|
+
data: request,
|
|
15
|
+
header: { Authorization: 'no-auth' },
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const defConfigService = new ConfigServiceImpl()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Taro from '@tarojs/taro'
|
|
2
|
+
import type {
|
|
3
|
+
DownloadFileRequest,
|
|
4
|
+
FileInfo,
|
|
5
|
+
MultiUploadFileResponse,
|
|
6
|
+
} from '../../rpc/base/v1/file'
|
|
7
|
+
import { uploadFile, uploadFileList } from '../../utils/file'
|
|
8
|
+
import { getRequestAccessToken, requestBaseURL } from '../../utils/http'
|
|
9
|
+
|
|
10
|
+
const FILE_URL = '/v1/base/file'
|
|
11
|
+
|
|
12
|
+
/** 文件服务。 */
|
|
13
|
+
export class FileServiceImpl {
|
|
14
|
+
/** 上传多个浏览器文件。 */
|
|
15
|
+
async MultiUploadFile(files: File[], fileType: string): Promise<MultiUploadFileResponse> {
|
|
16
|
+
if (process.env.TARO_ENV !== 'h5') {
|
|
17
|
+
throw new Error('小程序端请使用文件临时路径上传')
|
|
18
|
+
}
|
|
19
|
+
const paths = files.map((file) => URL.createObjectURL(file))
|
|
20
|
+
try {
|
|
21
|
+
return { files: await uploadFileList(fileType, paths) }
|
|
22
|
+
} finally {
|
|
23
|
+
paths.forEach((path) => URL.revokeObjectURL(path))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 上传单个浏览器文件。 */
|
|
28
|
+
async UploadFile(file: File, fileType: string): Promise<FileInfo> {
|
|
29
|
+
if (process.env.TARO_ENV !== 'h5') {
|
|
30
|
+
throw new Error('小程序端请使用文件临时路径上传')
|
|
31
|
+
}
|
|
32
|
+
const path = URL.createObjectURL(file)
|
|
33
|
+
try {
|
|
34
|
+
return await uploadFile(fileType, path)
|
|
35
|
+
} finally {
|
|
36
|
+
URL.revokeObjectURL(path)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 下载并打开后端文件。 */
|
|
41
|
+
async DownloadFile(file: string, fileName: string): Promise<void> {
|
|
42
|
+
const request: DownloadFileRequest = { name: fileName, path: file }
|
|
43
|
+
const query = Object.entries(request)
|
|
44
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
|
45
|
+
.join('&')
|
|
46
|
+
const token = await getRequestAccessToken()
|
|
47
|
+
const response = await Taro.downloadFile({
|
|
48
|
+
url: `${requestBaseURL}${FILE_URL}?${query}`,
|
|
49
|
+
header: { Authorization: token, 'source-client': 'miniapp' },
|
|
50
|
+
})
|
|
51
|
+
if (response.statusCode !== 200) throw new Error('下载失败')
|
|
52
|
+
await Taro.openDocument({ filePath: response.tempFilePath, showMenu: true })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 默认文件服务。 */
|
|
57
|
+
export const defFileService = new FileServiceImpl()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { http } from '../../utils/http'
|
|
2
|
+
import type {
|
|
3
|
+
CaptchaRequest,
|
|
4
|
+
CaptchaResponse,
|
|
5
|
+
LogoutRequest,
|
|
6
|
+
LoginRequest,
|
|
7
|
+
LoginResponse,
|
|
8
|
+
PasswordPublicKeyRequest,
|
|
9
|
+
PasswordPublicKeyResponse,
|
|
10
|
+
RefreshTokenRequest,
|
|
11
|
+
RefreshTokenResponse,
|
|
12
|
+
VerifyCaptchaRequest,
|
|
13
|
+
VerifyCaptchaResponse,
|
|
14
|
+
LoginService,
|
|
15
|
+
} from '../../rpc/base/v1/login'
|
|
16
|
+
import type { Empty } from '../../rpc/google/protobuf/empty'
|
|
17
|
+
|
|
18
|
+
const CAPTCHA_URL = '/v1/base/captcha'
|
|
19
|
+
const CAPTCHA_VERIFY_URL = '/v1/base/captcha/verify'
|
|
20
|
+
const PASSWORD_PUBLIC_KEY_URL = '/v1/base/password-public-key'
|
|
21
|
+
const SESSION_URL = '/v1/base/session'
|
|
22
|
+
const TOKEN_URL = '/v1/base/token'
|
|
23
|
+
|
|
24
|
+
/** 登录公共服务 */
|
|
25
|
+
export class LoginServiceImpl implements LoginService {
|
|
26
|
+
/** 验证码 */
|
|
27
|
+
Captcha(request: CaptchaRequest): Promise<CaptchaResponse> {
|
|
28
|
+
return http<CaptchaResponse>({
|
|
29
|
+
url: `${CAPTCHA_URL}`,
|
|
30
|
+
method: 'GET',
|
|
31
|
+
authMode: 'none',
|
|
32
|
+
data: request,
|
|
33
|
+
header: { Authorization: 'no-auth' },
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
/** 验证码预校验 */
|
|
37
|
+
VerifyCaptcha(request: VerifyCaptchaRequest): Promise<VerifyCaptchaResponse> {
|
|
38
|
+
return http<VerifyCaptchaResponse>({
|
|
39
|
+
url: `${CAPTCHA_VERIFY_URL}`,
|
|
40
|
+
method: 'POST',
|
|
41
|
+
authMode: 'none',
|
|
42
|
+
data: request,
|
|
43
|
+
header: { Authorization: 'no-auth' },
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
/** 获取密码临时公钥 */
|
|
47
|
+
PasswordPublicKey(request: PasswordPublicKeyRequest): Promise<PasswordPublicKeyResponse> {
|
|
48
|
+
return http<PasswordPublicKeyResponse>({
|
|
49
|
+
url: `${PASSWORD_PUBLIC_KEY_URL}`,
|
|
50
|
+
method: 'GET',
|
|
51
|
+
authMode: 'none',
|
|
52
|
+
data: request,
|
|
53
|
+
header: { Authorization: 'no-auth' },
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
/** 登出 */
|
|
57
|
+
Logout(request: LogoutRequest): Promise<Empty> {
|
|
58
|
+
return http<Empty>({
|
|
59
|
+
url: `${SESSION_URL}`,
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
authMode: 'required',
|
|
62
|
+
data: request,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
/** 刷新认证令牌 */
|
|
66
|
+
RefreshToken(request: RefreshTokenRequest): Promise<RefreshTokenResponse> {
|
|
67
|
+
return http<RefreshTokenResponse>({
|
|
68
|
+
url: `${TOKEN_URL}`,
|
|
69
|
+
method: 'POST',
|
|
70
|
+
authMode: 'none',
|
|
71
|
+
data: request,
|
|
72
|
+
header: { Authorization: 'no-auth' },
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
/** 登录 */
|
|
76
|
+
Login(request: LoginRequest): Promise<LoginResponse> {
|
|
77
|
+
return http<LoginResponse>({
|
|
78
|
+
url: `${SESSION_URL}`,
|
|
79
|
+
method: 'POST',
|
|
80
|
+
authMode: 'none',
|
|
81
|
+
data: request,
|
|
82
|
+
header: { Authorization: 'no-auth' },
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const defLoginService = new LoginServiceImpl()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { http } from '../../utils/http'
|
|
2
|
+
import type { ListBaseMenuResponse } from '../../rpc/system/app/v1/base_menu'
|
|
3
|
+
|
|
4
|
+
/** 远端移动菜单服务。 */
|
|
5
|
+
export class BaseMenuServiceImpl {
|
|
6
|
+
/** 获取扁平移动菜单配置。 */
|
|
7
|
+
ListBaseMenu(): Promise<ListBaseMenuResponse> {
|
|
8
|
+
return http<ListBaseMenuResponse>({
|
|
9
|
+
url: '/v1/app/base/menu',
|
|
10
|
+
method: 'GET',
|
|
11
|
+
authMode: 'optional',
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 默认移动菜单服务。 */
|
|
17
|
+
export const defBaseMenuService = new BaseMenuServiceImpl()
|