create-lumfall 1.0.0 → 1.1.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 (82) hide show
  1. package/README.md +24 -17
  2. package/cli.js +179 -24
  3. package/package.json +5 -7
  4. package/templates/basic/README.md +0 -113
  5. package/templates/basic/app/controller/demo.js +0 -28
  6. package/templates/basic/app/extend/README.md +0 -3
  7. package/templates/basic/app/middleware/README.md +0 -2
  8. package/templates/basic/app/middleware.js +0 -13
  9. package/templates/basic/app/pages/home/entry.home.js +0 -4
  10. package/templates/basic/app/pages/home/home.vue +0 -164
  11. package/templates/basic/app/router/demo.js +0 -17
  12. package/templates/basic/app/router-schema/demo.js +0 -29
  13. package/templates/basic/app/service/demo.js +0 -51
  14. package/templates/basic/app/webpack.config.js +0 -4
  15. package/templates/basic/build.js +0 -7
  16. package/templates/basic/config/config.beta.js +0 -4
  17. package/templates/basic/config/config.default.js +0 -32
  18. package/templates/basic/config/config.local.js +0 -4
  19. package/templates/basic/config/config.prod.js +0 -6
  20. package/templates/basic/package.json +0 -31
  21. package/templates/basic/server.js +0 -26
  22. package/templates/document/README.md +0 -145
  23. package/templates/document/app/extend/README.md +0 -6
  24. package/templates/document/app/middleware/README.md +0 -2
  25. package/templates/document/app/middleware.js +0 -2
  26. package/templates/document/app/pages/docs/assets/docs-logo.svg +0 -5
  27. package/templates/document/app/pages/docs/components/doc-layout.vue +0 -144
  28. package/templates/document/app/pages/docs/components/doc-navbar.vue +0 -103
  29. package/templates/document/app/pages/docs/components/doc-search.vue +0 -131
  30. package/templates/document/app/pages/docs/components/doc-sidebar.vue +0 -29
  31. package/templates/document/app/pages/docs/components/doc-toc.vue +0 -21
  32. package/templates/document/app/pages/docs/content.js +0 -28
  33. package/templates/document/app/pages/docs/docs-config.js +0 -154
  34. package/templates/document/app/pages/docs/docs.vue +0 -24
  35. package/templates/document/app/pages/docs/entry.docs.js +0 -26
  36. package/templates/document/app/pages/docs/markdown/highlight.js +0 -30
  37. package/templates/document/app/pages/docs/markdown/index.js +0 -129
  38. package/templates/document/app/pages/docs/search.js +0 -142
  39. package/templates/document/app/pages/docs/styles/docs.less +0 -1091
  40. package/templates/document/app/pages/docs/styles/vars.less +0 -87
  41. package/templates/document/app/pages/docs/theme.js +0 -47
  42. package/templates/document/app/pages/docs/utils.js +0 -58
  43. package/templates/document/app/pages/docs/views/doc-home.vue +0 -56
  44. package/templates/document/app/pages/docs/views/doc-page.vue +0 -147
  45. package/templates/document/app/webpack.config.js +0 -15
  46. package/templates/document/build.js +0 -6
  47. package/templates/document/config/config.beta.js +0 -2
  48. package/templates/document/config/config.default.js +0 -9
  49. package/templates/document/config/config.local.js +0 -2
  50. package/templates/document/config/config.prod.js +0 -2
  51. package/templates/document/docs/advanced/dashboard.md +0 -66
  52. package/templates/document/docs/advanced/health.md +0 -72
  53. package/templates/document/docs/advanced/monitoring.md +0 -60
  54. package/templates/document/docs/advanced/security.md +0 -88
  55. package/templates/document/docs/core/app-instance.md +0 -114
  56. package/templates/document/docs/core/controller-service.md +0 -113
  57. package/templates/document/docs/core/lifecycle.md +0 -60
  58. package/templates/document/docs/core/middleware.md +0 -83
  59. package/templates/document/docs/core/plugins.md +0 -77
  60. package/templates/document/docs/core/router-schema.md +0 -102
  61. package/templates/document/docs/dsl/api-contract.md +0 -88
  62. package/templates/document/docs/dsl/extend.md +0 -311
  63. package/templates/document/docs/dsl/menu.md +0 -101
  64. package/templates/document/docs/dsl/model-project.md +0 -122
  65. package/templates/document/docs/dsl/overview.md +0 -116
  66. package/templates/document/docs/dsl/reference.md +0 -175
  67. package/templates/document/docs/dsl/schema-actions.md +0 -135
  68. package/templates/document/docs/dsl/schema.md +0 -121
  69. package/templates/document/docs/frontend/build.md +0 -118
  70. package/templates/document/docs/frontend/curl.md +0 -75
  71. package/templates/document/docs/frontend/page.md +0 -99
  72. package/templates/document/docs/frontend/widgets.md +0 -151
  73. package/templates/document/docs/guide/config.md +0 -108
  74. package/templates/document/docs/guide/deployment.md +0 -115
  75. package/templates/document/docs/guide/getting-started.md +0 -199
  76. package/templates/document/docs/guide/introduction.md +0 -55
  77. package/templates/document/docs/guide/structure.md +0 -98
  78. package/templates/document/docs/reference/commands.md +0 -70
  79. package/templates/document/docs/reference/faq.md +0 -94
  80. package/templates/document/package.json +0 -36
  81. package/templates/document/scripts/build-static.js +0 -129
  82. package/templates/document/server.js +0 -13
@@ -1,87 +0,0 @@
1
- // 文档站设计变量:所有颜色走 CSS 变量,暗色模式通过 body[arco-theme="dark"]
2
- // 覆盖变量实现(Arco 组件的暗色也由同一属性驱动,两边自动同步)。
3
-
4
- :root {
5
- // 基础色板
6
- --doc-bg: #ffffff;
7
- --doc-bg-soft: #f7f8fa;
8
- --doc-navbar-bg: rgba(255, 255, 255, 0.86);
9
- --doc-sidebar-bg: #ffffff;
10
- --doc-text-1: #1d2129;
11
- --doc-text-2: #4e5969;
12
- --doc-text-3: #86909c;
13
- --doc-border: #e5e6eb;
14
- --doc-brand: #165dff;
15
- --doc-brand-soft: rgba(22, 93, 255, 0.08);
16
- --doc-mark-bg: rgba(22, 93, 255, 0.16);
17
-
18
- // 代码块
19
- --doc-code-bg: #f7f8fa;
20
- --doc-code-bar-bg: #f0f1f3;
21
- --doc-code-border: #e5e6eb;
22
- --doc-code-text: #24292f;
23
- --doc-inline-code-bg: rgba(22, 93, 255, 0.06);
24
- --doc-inline-code-text: #165dff;
25
-
26
- // highlight.js 语法高亮(亮色:接近 GitHub Light)
27
- --doc-hl-keyword: #cf222e;
28
- --doc-hl-built-in: #0550ae;
29
- --doc-hl-type: #953800;
30
- --doc-hl-literal: #0550ae;
31
- --doc-hl-number: #0550ae;
32
- --doc-hl-string: #0a3069;
33
- --doc-hl-comment: #6e7781;
34
- --doc-hl-function: #8250df;
35
- --doc-hl-tag: #116329;
36
- --doc-hl-attr: #0550ae;
37
- --doc-hl-variable: #953800;
38
- --doc-hl-meta: #6e7781;
39
- --doc-hl-section: #0550ae;
40
- --doc-hl-selector: #6639ba;
41
- --doc-hl-addition-bg: #dafbe1;
42
- --doc-hl-deletion-bg: #ffebe9;
43
-
44
- // 阴影
45
- --doc-shadow: 0 4px 10px rgba(0, 0, 0, 0.06);
46
- }
47
-
48
- body[arco-theme="dark"] {
49
- --doc-bg: #0d0e10;
50
- --doc-bg-soft: #17181a;
51
- --doc-navbar-bg: rgba(23, 24, 26, 0.86);
52
- --doc-sidebar-bg: #0d0e10;
53
- --doc-text-1: #f2f3f5;
54
- --doc-text-2: #c2c7cc;
55
- --doc-text-3: #8a9099;
56
- --doc-border: #333335;
57
- --doc-brand: #3c7eff;
58
- --doc-brand-soft: rgba(60, 126, 255, 0.16);
59
- --doc-mark-bg: rgba(60, 126, 255, 0.32);
60
-
61
- --doc-code-bg: #17181a;
62
- --doc-code-bar-bg: #1f2022;
63
- --doc-code-border: #333335;
64
- --doc-code-text: #e6e8eb;
65
- --doc-inline-code-bg: rgba(60, 126, 255, 0.14);
66
- --doc-inline-code-text: #84a8ff;
67
-
68
- // 暗色语法高亮(接近 GitHub Dark)
69
- --doc-hl-keyword: #ff7b72;
70
- --doc-hl-built-in: #79c0ff;
71
- --doc-hl-type: #ffa657;
72
- --doc-hl-literal: #79c0ff;
73
- --doc-hl-number: #79c0ff;
74
- --doc-hl-string: #a5d6ff;
75
- --doc-hl-comment: #8b949e;
76
- --doc-hl-function: #d2a8ff;
77
- --doc-hl-tag: #7ee787;
78
- --doc-hl-attr: #79c0ff;
79
- --doc-hl-variable: #ffa657;
80
- --doc-hl-meta: #8b949e;
81
- --doc-hl-section: #79c0ff;
82
- --doc-hl-selector: #d2a8ff;
83
- --doc-hl-addition-bg: rgba(46, 160, 67, 0.28);
84
- --doc-hl-deletion-bg: rgba(248, 81, 73, 0.24);
85
-
86
- --doc-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
87
- }
@@ -1,47 +0,0 @@
1
- // 主题(亮 / 暗)管理。Arco Design 通过 body 上的 arco-theme 属性切换暗色,
2
- // 文档站自己的样式全部走 CSS 变量(见 styles/vars.less),跟随该属性变化。
3
- const STORAGE_KEY = "lumfall-docs-theme";
4
-
5
- function apply(mode) {
6
- if (mode === "dark") {
7
- document.body.setAttribute("arco-theme", "dark");
8
- } else {
9
- document.body.removeAttribute("arco-theme");
10
- }
11
- }
12
-
13
- export function isDark() {
14
- return document.body.getAttribute("arco-theme") === "dark";
15
- }
16
-
17
- /** 进站时调用:优先用户上次选择,否则跟随系统偏好 */
18
- export function initTheme() {
19
- let saved = null;
20
- try {
21
- saved = localStorage.getItem(STORAGE_KEY);
22
- } catch (e) {
23
- // localStorage 不可用(如隐私模式)时只跟随系统
24
- }
25
- if (saved === "dark" || saved === "light") {
26
- apply(saved);
27
- return;
28
- }
29
- if (
30
- window.matchMedia &&
31
- window.matchMedia("(prefers-color-scheme: dark)").matches
32
- ) {
33
- apply("dark");
34
- }
35
- }
36
-
37
- /** 切换主题并持久化,返回切换后的模式 */
38
- export function toggleTheme() {
39
- const next = isDark() ? "light" : "dark";
40
- apply(next);
41
- try {
42
- localStorage.setItem(STORAGE_KEY, next);
43
- } catch (e) {
44
- // 忽略持久化失败
45
- }
46
- return next;
47
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * 标题 → url slug。保留中文等 unicode 字母数字,空格转 -,
3
- * 与 markdown 渲染器的标题 id 规则保持一致(搜索跳转锚点也用它)。
4
- */
5
- export function slugify(text) {
6
- return String(text)
7
- .trim()
8
- .toLowerCase()
9
- .replace(/[^\p{L}\p{N}\s-]/gu, "")
10
- .replace(/\s+/g, "-");
11
- }
12
-
13
- /** "/view/docs/guide/intro" → "guide/intro" */
14
- export function pathOfDoc(viewPath) {
15
- return String(viewPath || "")
16
- .replace(/^\/view\/docs\/?/, "")
17
- .replace(/\/+$/, "");
18
- }
19
-
20
- /**
21
- * 解析文档内相对链接(xxx.md / ./xxx.md / ../xxx.md,可带 #锚点)。
22
- * 返回 { path, hash };不是 .md 链接时返回 null(外链、页内锚点走默认规则)。
23
- * @param {string} currentDocPath 当前文档路径,如 "guide/getting-started"
24
- * @param {string} href markdown 里的 href
25
- */
26
- export function resolveDocHref(currentDocPath, href) {
27
- const [raw = "", hash = ""] = String(href).split("#");
28
- if (!/\.md$/.test(raw)) {
29
- return null;
30
- }
31
-
32
- const baseParts = String(currentDocPath).split("/").slice(0, -1);
33
- raw
34
- .replace(/^\.\//, "")
35
- .split("/")
36
- .forEach((seg) => {
37
- if (seg === "..") {
38
- baseParts.pop();
39
- } else if (seg && seg !== ".") {
40
- baseParts.push(seg);
41
- }
42
- });
43
-
44
- const resolved = baseParts.join("/").replace(/\.md$/, "");
45
- return {
46
- path: `/view/docs/${resolved}`,
47
- hash: hash ? `#${hash}` : "",
48
- };
49
- }
50
-
51
- /** HTML 转义(搜索高亮等场景) */
52
- export function escapeHtml(text) {
53
- return String(text)
54
- .replace(/&/g, "&")
55
- .replace(/</g, "&lt;")
56
- .replace(/>/g, "&gt;")
57
- .replace(/"/g, "&quot;");
58
- }
@@ -1,56 +0,0 @@
1
- <template>
2
- <DocLayout :sidebar="false">
3
- <div class="doc-home">
4
- <section class="doc-hero">
5
- <h1 class="doc-hero-name">{{ hero.name }}</h1>
6
- <p class="doc-hero-tagline">{{ hero.tagline }}</p>
7
- <p class="doc-hero-text">{{ hero.text }}</p>
8
- <div class="doc-hero-actions">
9
- <template v-for="action in hero.actions" :key="action.text">
10
- <router-link
11
- v-if="action.path"
12
- :to="action.path"
13
- class="doc-hero-btn"
14
- :class="action.theme || ''"
15
- >
16
- {{ action.text }}
17
- </router-link>
18
- <a
19
- v-else
20
- :href="action.link"
21
- target="_blank"
22
- rel="noopener noreferrer"
23
- class="doc-hero-btn"
24
- :class="action.theme || ''"
25
- >
26
- {{ action.text }}
27
- </a>
28
- </template>
29
- </div>
30
- </section>
31
-
32
- <section class="doc-features">
33
- <div v-for="feature in hero.features" :key="feature.title" class="doc-feature-card">
34
- <div class="doc-feature-icon">{{ feature.icon }}</div>
35
- <h3 class="doc-feature-title">{{ feature.title }}</h3>
36
- <p class="doc-feature-details">{{ feature.details }}</p>
37
- </div>
38
- </section>
39
-
40
- <footer v-if="footerText" class="doc-home-footer">{{ footerText }}</footer>
41
- </div>
42
- </DocLayout>
43
- </template>
44
-
45
- <script setup>
46
- import { computed } from "vue";
47
- import DocLayout from "../components/doc-layout.vue";
48
- import docsConfig from "../docs-config";
49
-
50
- const hero = docsConfig.hero;
51
- const footerText = computed(
52
- () => docsConfig.footer.text || docsConfig.footer.copyright || ""
53
- );
54
-
55
- document.title = docsConfig.site.description;
56
- </script>
@@ -1,147 +0,0 @@
1
- <template>
2
- <DocLayout :sidebar="true">
3
- <template v-if="source !== undefined">
4
- <div class="doc-content-wrap">
5
- <article class="doc-markdown" v-html="rendered.html"></article>
6
-
7
- <div class="doc-pager">
8
- <router-link v-if="prev" :to="prev.path" class="doc-pager-item prev">
9
- <span class="doc-pager-label">上一篇</span>
10
- <span class="doc-pager-title"><icon-left /> {{ prev.text }}</span>
11
- </router-link>
12
- <span v-else class="doc-pager-item placeholder"></span>
13
-
14
- <router-link v-if="next" :to="next.path" class="doc-pager-item next">
15
- <span class="doc-pager-label">下一篇</span>
16
- <span class="doc-pager-title">{{ next.text }} <icon-right /></span>
17
- </router-link>
18
- </div>
19
-
20
- <div v-if="footer.text" class="doc-content-footer">{{ footer.text }}</div>
21
- </div>
22
-
23
- <DocToc class="doc-toc-rail" :items="rendered.toc" :active-id="activeHeadingId" />
24
- </template>
25
-
26
- <div v-else class="doc-content-wrap">
27
- <div class="doc-not-found">
28
- <h1>404</h1>
29
- <p>没有找到文档:<code>/{{ docPath }}</code></p>
30
- <p>
31
- 检查 docs/ 目录下是否存在对应的 markdown 文件,以及
32
- docs-config.js 的侧边栏路径是否正确。
33
- </p>
34
- <router-link to="/view/docs" class="doc-not-found-home">返回首页</router-link>
35
- </div>
36
- </div>
37
- </DocLayout>
38
- </template>
39
-
40
- <script setup>
41
- import { computed, onBeforeUnmount, onMounted, ref, watch, nextTick } from "vue";
42
- import { useRoute } from "vue-router";
43
- import DocLayout from "../components/doc-layout.vue";
44
- import DocToc from "../components/doc-toc.vue";
45
- import { renderMarkdown } from "../markdown/index";
46
- import { getDocSource } from "../content";
47
- import docsConfig from "../docs-config";
48
- import { pathOfDoc } from "../utils";
49
-
50
- const route = useRoute();
51
-
52
- const docPath = computed(() => pathOfDoc(decodeURIComponent(route.params.path || "")));
53
- const source = computed(() => getDocSource(docPath.value));
54
- const rendered = computed(() =>
55
- source.value === undefined
56
- ? { html: "", toc: [] }
57
- : renderMarkdown(source.value, docPath.value)
58
- );
59
- const footer = docsConfig.footer;
60
-
61
- const activeHeadingId = ref("");
62
-
63
- // ---- 上一篇 / 下一篇:按侧边栏顺序 ----
64
- const flatItems = docsConfig.sidebar.flatMap((group) =>
65
- (group.items || []).map((item) => ({
66
- text: item.text,
67
- path: item.path,
68
- docPath: pathOfDoc(item.path),
69
- }))
70
- );
71
-
72
- const currentIndex = computed(() =>
73
- flatItems.findIndex((item) => item.docPath === docPath.value)
74
- );
75
- const prev = computed(() =>
76
- currentIndex.value > 0 ? flatItems[currentIndex.value - 1] : null
77
- );
78
- const next = computed(() =>
79
- currentIndex.value >= 0 && currentIndex.value < flatItems.length - 1
80
- ? flatItems[currentIndex.value + 1]
81
- : null
82
- );
83
-
84
- // ---- 标题与描述 ----
85
- watch(
86
- docPath,
87
- (path) => {
88
- const sidebarItem = flatItems.find((item) => item.docPath === path);
89
- document.title = sidebarItem
90
- ? `${sidebarItem.text} · ${docsConfig.site.title}`
91
- : docsConfig.site.description;
92
- },
93
- { immediate: true }
94
- );
95
-
96
- // ---- 路由变化:滚动到锚点或页首 ----
97
- watch(
98
- docPath,
99
- () => {
100
- activeHeadingId.value = "";
101
- nextTick(() => {
102
- if (route.hash) {
103
- const el = document.getElementById(decodeURIComponent(route.hash.slice(1)));
104
- if (el) {
105
- el.scrollIntoView();
106
- return;
107
- }
108
- }
109
- window.scrollTo(0, 0);
110
- });
111
- },
112
- { immediate: true }
113
- );
114
-
115
- // ---- TOC 滚动联动(滚动监听当前所在小节)----
116
- let ticking = false;
117
-
118
- const updateActiveHeading = () => {
119
- ticking = false;
120
- const headings = document.querySelectorAll(
121
- ".doc-markdown h2[id], .doc-markdown h3[id]"
122
- );
123
- let current = "";
124
- headings.forEach((el) => {
125
- if (el.getBoundingClientRect().top <= 96) {
126
- current = el.id;
127
- }
128
- });
129
- activeHeadingId.value = current;
130
- };
131
-
132
- const onScroll = () => {
133
- if (!ticking) {
134
- ticking = true;
135
- window.requestAnimationFrame(updateActiveHeading);
136
- }
137
- };
138
-
139
- onMounted(() => {
140
- window.addEventListener("scroll", onScroll, { passive: true });
141
- updateActiveHeading();
142
- });
143
-
144
- onBeforeUnmount(() => {
145
- window.removeEventListener("scroll", onScroll);
146
- });
147
- </script>
@@ -1,15 +0,0 @@
1
- // 业务 Webpack 扩展配置:与框架基础配置 webpack-merge.merge.smart 合并。
2
- //
3
- // 文档站把项目根目录 docs/ 下的 markdown 按原始文本打进产物
4
- // (页面里通过 require.context 读取),因此在这里声明 asset/source 规则。
5
- module.exports = {
6
- module: {
7
- rules: [
8
- {
9
- test: /\.md$/,
10
- // webpack 5 内置资源模块:把文件内容作为字符串导出(default export)
11
- type: "asset/source",
12
- },
13
- ],
14
- },
15
- };
@@ -1,6 +0,0 @@
1
- const { frontendBuild } = require("lumfall");
2
-
3
- // 环境由 _ENV 决定(不是 NODE_ENV):
4
- // _ENV=local 启动 Webpack dev server(HMR 热更新,默认 127.0.0.1:9002)
5
- // _ENV=prod 产物构建到 app/public/dist/prod/,同时产出各页面 .tpl 模板
6
- frontendBuild(process.env._ENV);
@@ -1,2 +0,0 @@
1
- // _ENV=beta 时加载,覆盖 config.default.js 的同名键(浅合并)
2
- module.exports = {};
@@ -1,9 +0,0 @@
1
- // 所有环境共享的基础配置。合并顺序(后者覆盖前者同名键):
2
- // 框架 config.default -> 业务 config.default -> 框架 config.<env> -> 业务 config.<env>
3
- module.exports = {
4
- name: "lumfall-document",
5
- apiBasePath: "/api",
6
-
7
- // 文档站是纯静态站点,默认不暴露 /api 路由;security 的默认值
8
- // (签名关闭 + projectKey 开启)保持框架默认即可,见框架文档「安全策略」
9
- };
@@ -1,2 +0,0 @@
1
- // _ENV=local 时加载,覆盖 config.default.js 的同名键(浅合并)
2
- module.exports = {};
@@ -1,2 +0,0 @@
1
- // _ENV=prod 时加载,覆盖 config.default.js 的同名键(浅合并)
2
- module.exports = {};
@@ -1,66 +0,0 @@
1
- # Dashboard 与 Model 配置
2
-
3
- 框架自带开箱即用的 B 端控制台页面 `/view/dashboard`。它本身**不含任何业务代码**:
4
- 页面结构、菜单、每个入口的形态,全部由业务项目 `model/` 目录下的
5
- **Dashboard DSL** 声明,启动时自动扫描合并。
6
-
7
- ::: tip 编写配置请看 DSL 章节
8
- 本页讲 Dashboard 页面如何**消费** DSL;如何**编写** DSL(Model / Project /
9
- 菜单 / schema 模块 / 接口契约)见 [DSL 章节](../dsl/overview.md)。
10
- :::
11
-
12
- ## 前端消费链路
13
-
14
- ```
15
- 1. 浏览器访问 /view/dashboard/schema?projectKey=pdd&key=product
16
- (history 模式,服务端由 /view/:page/* 兜底渲染)
17
- 2. dashboard.vue 挂载:
18
- ├── GET /api/project/list?projectKey=pdd → 项目列表(头部项目切换)
19
- └── GET /api/project?projectKey=pdd → 合并后的完整项目配置(含 menu)
20
- 3. menuStore.setMenuList(menu) → 菜单存入 Pinia
21
- 4. header-view 渲染菜单 → 含 subMenu 的项渲染为下拉子菜单
22
- 5. 点击菜单项 → 按 moduleType 决定路由跳转
23
- ├── custom → dashboard 基址 + customConfig.path
24
- ├── sider → /sider(左侧子菜单,子项跳转带 siderKey)
25
- ├── iframe → /iframe
26
- └── schema → /schema → schema-view
27
- 6. schema-view 内部:
28
- useSchema() 按路由 query 的 key / siderKey 从 menuStore 找到菜单项
29
- → buildDtoSchema 按 xxxOption 拆出表格 / 搜索 / 动态组件 schema
30
- → 搜索栏 + 表格渲染,URL query 同名字段预填搜索默认值
31
- 7. 动态组件(配置了 tableConfig.componentConfig 时):
32
- 按钮 eventKey=showComponent 打开 createForm / editForm / detailPanel 抽屉
33
- → 保存成功 emit loadTableData → 表格刷新
34
- ```
35
-
36
- ## Dashboard 前端路由
37
-
38
- | 路由(基址 `/view/dashboard`) | 组件 | 说明 |
39
- | --- | --- | --- |
40
- | `/sider` | sider-view | 侧边复合视图,含子路由 `/sider/iframe`、`/sider/schema` 等 |
41
- | `/iframe` | iframe-view | iframe 嵌入页 |
42
- | `/schema` | schema-view | schema 驱动页 |
43
- | `/sider/:chapters+` | sider-view | sider 多级路径兜底 |
44
-
45
- ## 内置数据接口
46
-
47
- | 接口 | 免 project_key | 说明 |
48
- | --- | --- | --- |
49
- | `GET /api/project/model_list` | ✓ | 全部 Model 及其 Project 概要(DTO) |
50
- | `GET /api/project/list?projectKey=` | ✓ | 项目列表(可按 key 过滤) |
51
- | `GET /api/project?projectKey=` | ✗(需 `project_key` 头) | 合并后的完整项目配置(含 menu) |
52
-
53
- Dashboard 页面启动时用 `$lumfallCurl` 拉取这些接口渲染菜单;URL 带
54
- `?projectKey=xxx` 时 curl 自动带 `project_key` 头(见[请求工具](../frontend/curl.md))。
55
-
56
- ## 项目切换
57
-
58
- 头部右上角的下拉会列出**同 Model 下的其他 Project**(来自
59
- `/api/project/list`),切换后以新项目的 `homePage` 作为落点——这就是
60
- Model + Project 两层 DSL 在体验上的意义:一套公共菜单骨架,多个项目按需覆盖。
61
-
62
- ## 下一步
63
-
64
- - [DSL 总览](../dsl/overview.md):从零声明一个管理台
65
- - [接口契约](../dsl/api-contract.md):schema 模块的后端接口标准
66
- - [安全策略](./security.md):`/api/project/*` 的 project_key 校验
@@ -1,72 +0,0 @@
1
- # 健康检查
2
-
3
- 框架自带两个健康检查接口与一个探针注册器,用于容器编排 / 负载均衡的探活。
4
-
5
- ## 内置接口
6
-
7
- | 接口 | 用途 | 行为 |
8
- | --- | --- | --- |
9
- | `GET /health/live` | 存活探针 | 进程能响应即 200 `{"status":"ok"}` |
10
- | `GET /health/ready` | 就绪探针 | 并行执行全部已注册探针,全部通过 200;任一抛错 / 超时 / 返回 `false` 则 503 |
11
-
12
- 两个接口都带 `Cache-Control: no-store`。`/health/ready` 的响应只包含探针名与状态,
13
- 不包含错误详情(避免泄露内部信息):
14
-
15
- ```json
16
- {
17
- "status": "error",
18
- "checks": [
19
- { "name": "database", "status": "ok" },
20
- { "name": "cache", "status": "error" }
21
- ]
22
- }
23
- ```
24
-
25
- `/view/health` 是框架自带的人工查看页面。
26
-
27
- ## 注册业务探针
28
-
29
- 通过 extend 把业务依赖(数据库、缓存、下游服务)注册为探针:
30
-
31
- ```js
32
- // app/extend/health-check.js
33
- module.exports = (app) => {
34
- app.health.register("database", async () => {
35
- await app.services.db.ping(); // 抛错或返回 false 视为不健康
36
- });
37
-
38
- app.health.register("cache", async () => {
39
- const ok = await redisClient.ping();
40
- return ok === "PONG";
41
- }, { timeoutMs: 1000 }); // 默认 3000ms
42
-
43
- return {}; // extend 的返回值挂到 app.healthCheck(返回 {} 占位即可)
44
- };
45
- ```
46
-
47
- 探针要求:
48
-
49
- - `register(name, probe, { timeoutMs })`:name 唯一非空;probe 是返回
50
- Promise / 布尔的函数;抛错、超时、返回 `false` 都判为不健康
51
- - 同名重复注册会启动失败
52
- - `app.health.list()` 查看已注册探针(也会出现在
53
- `app.diagnostics.getManifest().healthChecks`)
54
-
55
- ## 接入编排系统
56
-
57
- ```yaml
58
- # Kubernetes 示例
59
- livenessProbe:
60
- httpGet:
61
- path: /health/live
62
- port: 3000
63
- readinessProbe:
64
- httpGet:
65
- path: /health/ready
66
- port: 3000
67
- ```
68
-
69
- - 存活探针只证明进程能响应,不要挂重探针
70
- - 就绪探针反映真实依赖状态:依赖故障时摘除流量,但不重启进程
71
- - 文档站这类无外部依赖的纯静态项目,不注册探针即可,`/health/ready`
72
- 恒为 200(`checks` 为空)
@@ -1,60 +0,0 @@
1
- # 请求观测 monitoring
2
-
3
- `serviceStart({ monitoring })` 提供请求级的观测钩子(可选配置,
4
- 不配置就是纯 passthrough,对默认行为零影响)。
5
-
6
- ## 基本用法
7
-
8
- ```js
9
- serviceStart({
10
- monitoring: {
11
- // 透传 / 生成 trace id 的请求头,默认 "x-trace-id"
12
- traceHeader: "x-trace-id",
13
-
14
- onRequestStart({ traceId, method, path }) {
15
- // 请求进入
16
- },
17
- onRequestEnd({ traceId, method, path, status, durationMs }) {
18
- // 响应完成(正常路径)
19
- },
20
- onRequestError({ traceId, method, path, error, durationMs }) {
21
- // 内层抛出异常时(异常还会继续向上交给 errorHandler)
22
- },
23
- },
24
- });
25
- ```
26
-
27
- ## trace id
28
-
29
- - 请求头里已有 `traceHeader` 就复用,否则生成(UUID),并回显到**同名响应头**
30
- - trace id 同时写入 `ctx.traceId`,业务代码可直接读
31
- - 跨服务串日志:上游把 trace id 放进请求头,下游自动复用
32
-
33
- ## 行为约定
34
-
35
- - 钩子自己抛错只记 warning,**不影响响应**——观测故障不拖垮业务
36
- - monitoring 位于 `errorHandler` 内侧:内层异常先触发 `onRequestError`,
37
- 再由 errorHandler 渲染响应,所以每个请求都会且只会触发一次
38
- `onRequestEnd` 或 `onRequestError`
39
- - 校验:未知 hook 名、非函数 hook、非法 `traceHeader` 都会导致**启动失败**
40
-
41
- ## 典型用法
42
-
43
- ```js
44
- serviceStart({
45
- monitoring: {
46
- onRequestStart({ traceId, method, path }) {
47
- app.logger.info(`[start] ${traceId} ${method} ${path}`);
48
- },
49
- onRequestEnd({ traceId, method, path, status, durationMs }) {
50
- app.logger.info(`[end] ${traceId} ${method} ${path} ${status} ${durationMs}ms`);
51
- },
52
- },
53
- });
54
- ```
55
-
56
- ::: tip 与全局中间件的分工
57
- - monitoring:请求级「开始 / 结束 / 出错」三点观测 + trace 透传
58
- - 业务全局中间件:需要修改请求 / 响应(加 header、改 body)的逻辑
59
- 只做观测就用 monitoring,它有框架级的错误隔离。
60
- :::