neo-cmp-cli 1.9.7 → 1.9.8

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.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("node:fs"),t=require("node:path"),s=require("lodash"),o=require("akfun"),r=require("ora"),a=require("./neoService.js"),i=require("../utils/common.js"),n=require("../utils/pathUtils.js"),c=require("../utils/projectUtils/createCmpProjectZip.js");var l,p;exports.__require=function(){if(p)return l;p=1;const u=e,g=t,m=s,{getConfigObj:d}=o,w=r,y=a.__require(),{getFramework:f,errorLog:h,successLog:v}=i.__require(),{catchCurPackageJson:b}=n.__require(),T=c.__require(),C=d(b()),$=(e=[])=>e.map(e=>({label:e.label,description:e.description,propSchema:JSON.stringify(e)})),P=(e=[],t)=>e&&0!==e.length?e.map(e=>({...e,componentType:t,eventCategory:e.eventCategory||2,pageType:e.pageType||1,targetDevice:e.targetDevice||1,eventParams:e.eventParams||[]})):[],q=(e=[],t)=>e&&0!==e.length?e.map(e=>({...e,componentType:t,funcScope:e.funcScope||"all",pageType:e.pageType||1,targetDevice:e.targetDevice||1,custom:void 0===e.custom||e.custom,funcInParams:e.funcInParams||[],funcOutParams:e.funcOutParams||[]})):[];return l=async(e,t)=>{const s=w("正在发布组件...").start();try{let o,r=new y(e);await r.ensureValidToken(),s.start("[1/4] 打包源码文件(含单个自定义组件源码)...");try{o=T(t,process.cwd(),e.assetsRoot),o?v(`[1/4] 源码文件打包完成: ${g.basename(o)}。`,s):h("[1/4] 源码文件打包失败,未返回 zip 文件路径。",s)}catch(e){h("[1/4] 源码文件打包失败。",s)}s.start("[2/4] 获取自定义组件构建产物...");const a=await r.getCmpAssets(t);let i;s.start("[3/4] 构建组件数据...");try{i=await(async(e,t)=>{if(!t||!t.cmpType)return h("自定义组件信息或组件名称不能为空"),null;const{cmpType:s}=t;if(!e||!u.existsSync(e))return h(`未找到自定义组件目录: ${e}`),null;const o=m.camelCase(s),r=g.join(e,`${o}Model.js`),a=globalThis.window;globalThis.window||(globalThis.window={console:console,neoRequire:()=>{},postMessage:()=>{}});try{u.existsSync(r)?require(r):(h(`未找到自定义组件模型文件,请检查以下路径是否存在:${r}`),process.exit(1)),globalThis.window&&globalThis.window.NEOEditorCustomModels||(h(`模型文件未导出有效模型方法(CatchCustomCmpModelClass),模型文件地址: ${r} `),process.exit(1));const e=globalThis.window.NEOEditorCustomModels[s];e||(h(`未找到自定义组件模型类(${s}),模型文件地址: ${r} `),process.exit(1));const o=new e;o||(h(`未找到自定义组件模型信息(${s}),模型文件地址: ${r} `),process.exit(1));const a={...t,version:C.version||"1.0.0",framework:C.framework?f(C.framework):0,label:o.label||s,description:o.description||"",componentCategory:(o.tags||["自定义组件"]).join(","),targetPage:o.targetPage||["customPage"],targetObject:o.targetObject||["all"],targetApplication:o.targetApplication||["all"],targetDevice:o.targetDevice||"all",iconUrl:o.iconUrl||o.iconUrl,defaultProps:JSON.stringify(o.defaultComProps||{}),previewProps:JSON.stringify(o.previewComProps||{}),props:$(o.propsSchema||[]),events:P(o.events||[],s),functions:q(o.functions||o.actions||[],s),exposedToDesigner:void 0===o.exposedToDesigner||o.exposedToDesigner,namespace:o.namespace||"neo-cmp-cli",enableDuplicate:void 0===o.enableDuplicate||o.enableDuplicate};return console.log(`自定义组件模型信息(${s}):`,m.omit(a,["assetFile","modelAssetFile","cssAssetFile","codeLibFile"])),a}catch(e){return h(`自定义组件模型文件解析失败 (${r||"未知路径"}): ${e.message}`),h(e.stack),null}finally{void 0===a?delete globalThis.window:globalThis.window=a}})(e.assetsRoot,a),i?v("[3/4] 组件数据构建完成。",s):h(`[3/4] 未获取到自定义组件模型信息(${t})。`,s)}catch(e){h(`[3/4] 组件数据构建失败: ${e.message}`,s)}s.start("[4/4] 保存组件信息到 NeoCRM 平台...");try{await r.updateCustomComponent(i),v("[4/4] 组件信息保存成功",s)}catch(e){h("[4/4] 组件信息保存失败",s)}const{tenant_id:n,instance_uri:c}=r.tokenCache||{};console.log(`\n✅ 自定义组件发布成功!\n(当前租户 ID: ${n||"未返回"},所在租户的 API 域名: ${c||"未返回"}。)`)}catch(e){try{s&&s.isSpinning&&h(`❌ 自定义组件发布失败: ${e.message}`,s)}catch{h(`\n❌ 自定义组件发布失败: ${e.message}`)}throw e}},l};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("node:fs"),t=require("node:path"),s=require("lodash"),o=require("akfun"),r=require("ora"),a=require("./neoService.js"),i=require("../utils/common.js"),n=require("../utils/pathUtils.js"),c=require("../utils/projectUtils/createCmpProjectZip.js");var l,p;exports.__require=function(){if(p)return l;p=1;const u=e,g=t,m=s,{getConfigObj:d}=o,w=r,y=a.__require(),{getFramework:f,errorLog:h,successLog:b}=i.__require(),{catchCurPackageJson:v}=n.__require(),T=c.__require(),C=d(v()),$=(e=[])=>e.map(e=>({label:e.label,description:e.description,propSchema:JSON.stringify(e)})),P=(e=[],t)=>e&&0!==e.length?e.map(e=>({...e,componentType:t,eventCategory:e.eventCategory||2,pageType:e.pageType||1,targetDevice:e.targetDevice||1,eventParams:e.eventParams||[]})):[],q=(e=[],t)=>e&&0!==e.length?e.map(e=>({...e,componentType:t,funcScope:e.funcScope||"all",pageType:e.pageType||1,targetDevice:e.targetDevice||1,custom:!0,funcInParams:e.funcInParams||[],funcOutParams:e.funcOutParams||[]})):[];return l=async(e,t)=>{const s=w("正在发布组件...").start();try{let o,r=new y(e);await r.ensureValidToken(),s.start("[1/4] 打包源码文件(含单个自定义组件源码)...");try{o=T(t,process.cwd(),e.assetsRoot),o?b(`[1/4] 源码文件打包完成: ${g.basename(o)}。`,s):h("[1/4] 源码文件打包失败,未返回 zip 文件路径。",s)}catch(e){h("[1/4] 源码文件打包失败。",s)}s.start("[2/4] 获取自定义组件构建产物...");const a=await r.getCmpAssets(t);let i;s.start("[3/4] 构建组件数据...");try{i=await(async(e,t)=>{if(!t||!t.cmpType)return h("自定义组件信息或组件名称不能为空"),null;const{cmpType:s}=t;if(!e||!u.existsSync(e))return h(`未找到自定义组件目录: ${e}`),null;const o=m.camelCase(s),r=g.join(e,`${o}Model.js`),a=globalThis.window;globalThis.window||(globalThis.window={console:console,neoRequire:()=>{},postMessage:()=>{}});try{u.existsSync(r)?require(r):(h(`未找到自定义组件模型文件,请检查以下路径是否存在:${r}`),process.exit(1)),globalThis.window&&globalThis.window.NEOEditorCustomModels||(h(`模型文件未导出有效模型方法(CatchCustomCmpModelClass),模型文件地址: ${r} `),process.exit(1));const e=globalThis.window.NEOEditorCustomModels[s];e||(h(`未找到自定义组件模型类(${s}),模型文件地址: ${r} `),process.exit(1));const o=new e;o||(h(`未找到自定义组件模型信息(${s}),模型文件地址: ${r} `),process.exit(1));const a={...t,version:C.version||"1.0.0",framework:C.framework?f(C.framework):0,label:o.label||s,description:o.description||"",componentCategory:(o.tags||["自定义组件"]).join(","),targetPage:o.targetPage||["customPage"],targetObject:o.targetObject||["all"],targetApplication:o.targetApplication||["all"],targetDevice:o.targetDevice||"all",iconUrl:o.iconUrl||o.iconUrl,defaultProps:JSON.stringify(o.defaultComProps||{}),previewProps:JSON.stringify(o.previewComProps||{}),props:$(o.propsSchema||[]),events:P(o.events||[],s),functions:q(o.functions||o.actions||[],s),exposedToDesigner:void 0===o.exposedToDesigner||o.exposedToDesigner,namespace:o.namespace||"neo-cmp-cli",enableDuplicate:void 0===o.enableDuplicate||o.enableDuplicate};return console.log(`自定义组件模型信息(${s}):`,m.omit(a,["assetFile","modelAssetFile","cssAssetFile","codeLibFile"])),a}catch(e){return h(`自定义组件模型文件解析失败 (${r||"未知路径"}): ${e.message}`),h(e.stack),null}finally{void 0===a?delete globalThis.window:globalThis.window=a}})(e.assetsRoot,a),i?b("[3/4] 组件数据构建完成。",s):h(`[3/4] 未获取到自定义组件模型信息(${t})。`,s)}catch(e){h(`[3/4] 组件数据构建失败: ${e.message}`,s)}s.start("[4/4] 保存组件信息到 NeoCRM 平台...");try{await r.updateCustomComponent(i),b("[4/4] 组件信息保存成功",s)}catch(e){h("[4/4] 组件信息保存失败",s)}const{tenant_id:n,instance_uri:c}=r.tokenCache||{};console.log(`\n✅ 自定义组件发布成功!\n(当前租户 ID: ${n||"未返回"},所在租户的 API 域名: ${c||"未返回"}。)`)}catch(e){try{s&&s.isSpinning&&h(`❌ 自定义组件发布失败: ${e.message}`,s)}catch{h(`\n❌ 自定义组件发布失败: ${e.message}`)}throw e}},l};
@@ -1 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.9.7";const o={version:e};exports.default=o,exports.version=e;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.9.8";const o={version:e};exports.default=o,exports.version=e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-cmp-cli",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Neo 自定义组件开发工具,支持react 和 vue2.0技术栈。",
5
5
  "keywords": [
6
6
  "neo-cli",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "adm-zip": "^0.5.10",
45
- "akfun": "^5.2.5",
45
+ "akfun": "^5.2.12",
46
46
  "axios": "^0.27.2",
47
47
  "babel-plugin-import": "^1.13.8",
48
48
  "chalk": "^4.0.0",
@@ -0,0 +1,469 @@
1
+ # ComTree 生成过程分析
2
+
3
+ ## 一、概述
4
+
5
+ ComTree 是 Neo UI 框架中元数据能力的载体,负责元数据的加载、解析,基于模型的扩展的加载、解析、合并。它是一个树形结构,每个节点都是一个 `Com` 实例,代表一个组件或布局。
6
+
7
+ ## 二、核心概念
8
+
9
+ ### 2.1 Com 类
10
+ - `Com` 是树形节点,任何一个 com 都可以向下展开
11
+ - Com 是整个元数据实现的骨架,组合 kernel 中的能力构建出立体元数据的解析执行运行时
12
+ - Com 继承自 `XNode`,具备树形结构的基本能力
13
+
14
+ ### 2.2 节点类型
15
+ - **PageCom**: 页面级别的 Com,是整个页面的根节点
16
+ - **LayoutCom**: 布局 Com,负责管理子组件的布局
17
+ - **DataServiceCom**: 数据服务 Com,负责数据获取和管理
18
+ - **普通 Com**: 业务组件 Com
19
+
20
+ ### 2.3 生命周期阶段
21
+ ```typescript
22
+ enum LifePhase {
23
+ INIT = 0, // 初始化
24
+ ATTACHED = 1, // 已挂载到树
25
+ READY = 2, // 准备就绪
26
+ DETACHED = 3, // 已卸载
27
+ DESTROYED = 4 // 已销毁
28
+ }
29
+ ```
30
+
31
+ ## 三、ComTree 生成流程
32
+
33
+ ### 3.1 整体流程
34
+
35
+ ```
36
+ 1. 页面打开请求
37
+
38
+ 2. 创建 PageCom 实例
39
+
40
+ 3. 创建 PageStore (数据服务)
41
+
42
+ 4. Store 加载布局数据 (layoutData)
43
+
44
+ 5. 调用 $expandSubTree 展开子树
45
+
46
+ 6. 递归创建子 Com 节点
47
+
48
+ 7. 应用扩展 (AgentCom)
49
+
50
+ 8. 设置变量空间
51
+
52
+ 9. 标记 ready2render = true
53
+
54
+ 10. 渲染到 UI
55
+ ```
56
+
57
+ ### 3.2 详细步骤
58
+
59
+ #### 步骤 1: 页面打开
60
+
61
+ 在 `NeoRenderer` 或 `PageModelRender` 中,通过以下方式打开页面:
62
+
63
+ ```typescript
64
+ // packages/neo-ui-common/src/_comTreeDom/pageModelRender.tsx
65
+ this.pageCom = NeoApp.openPageByPageInfo({
66
+ pageKey,
67
+ pageEngine,
68
+ cmpType: pageKey,
69
+ pageParams: queryObject
70
+ })
71
+ ```
72
+
73
+ #### 步骤 2: 创建 PageCom 实例
74
+
75
+ ```typescript
76
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/pageCom.ts
77
+ class PageCom extends Com {
78
+ constructor(comArgs: ComArgs<IPageConfig>) {
79
+ super(comArgs)
80
+ this[SCOPE] = { page: this } as ComScopeNode
81
+ this.config = comArgs.props as IPageConfig
82
+ SystemCtx.initPagePrivateScope(this)
83
+ }
84
+ }
85
+ ```
86
+
87
+ #### 步骤 3: 创建 Store
88
+
89
+ 在 `Com` 构造函数中,通过 `queueMicrotask` 异步创建 Store:
90
+
91
+ ```typescript
92
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/com.ts:294-296
93
+ queueMicrotask(() => {
94
+ this.$tryCreateStore()
95
+ })
96
+ ```
97
+
98
+ PageCom 重写了 `$tryCreateStore` 方法:
99
+
100
+ ```typescript
101
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/pageCom.ts:182-195
102
+ async $tryCreateStore() {
103
+ const { pageEngine, pageKey } = this.props || {}
104
+ const storeCls = CmpStoreRegister.getStoreClass(this.cmpType)
105
+ || CmpStoreRegister.getBasePageStoreClass()
106
+ const store = new storeCls(this)
107
+ this[BOUNDED].boundStore = store
108
+ this[STATE].storeResolved = true
109
+ await store.init()
110
+ store.status = 'init'
111
+ }
112
+ ```
113
+
114
+ #### 步骤 4: 启动数据服务
115
+
116
+ 在表单页面中,通过 `dataServiceStart` 方法启动数据服务:
117
+
118
+ ```typescript
119
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/pageCom/formPageUniCom.ts:178-246
120
+ async dataServiceStart(initProps: any = {}) {
121
+ // 1. 应用页面扩展
122
+ await AgentComManager.applyPageAgent(this)
123
+
124
+ // 2. 加载布局数据
125
+ let layoutData = await this.$store.loadLayout()
126
+
127
+ // 3. 权限检查
128
+ let checkAccess = await this.preRenderCheck(...)
129
+
130
+ // 4. 构建 ComTree
131
+ if (layoutData?.layout !== null) {
132
+ let cmp = layoutData.layout
133
+ await this.$expandSubTree([cmp])
134
+
135
+ // 5. 应用扩展
136
+ this.applyAgentCom()
137
+ this.setupVarSpace()
138
+ this.comsReady = true
139
+ }
140
+ }
141
+ ```
142
+
143
+ #### 步骤 5: 展开子树 ($expandSubTree)
144
+
145
+ 这是 ComTree 生成的核心方法:
146
+
147
+ ```typescript
148
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/com.ts:1586-1617
149
+ $expandSubTree(subCmps: ComArgs[]) {
150
+ // 校验逻辑
151
+ if (!subCmps || subCmps.length === 0) {
152
+ this.children = []
153
+ lifecycleInvoking(this, 'ready')
154
+ return
155
+ }
156
+
157
+ // 递归展开
158
+ subCmps.forEach((cmp) => {
159
+ // 1. 根据 cmpType 查找对应的 Com 类
160
+ let ComClass = ComFinder.getCom({ cmpType: cmp.cmpType })
161
+
162
+ // 2. 创建子 Com 实例
163
+ let childCom = new ComClass(cmp)
164
+
165
+ // 3. 将子节点添加到当前节点
166
+ this.$appendChild(childCom)
167
+
168
+ // 4. 如果还有子节点,递归展开
169
+ if (cmp.subCmps && cmp.subCmps.length) {
170
+ childCom.$expandSubTree(cmp.subCmps)
171
+ } else {
172
+ // 5. 没有子节点,标记为 READY
173
+ childCom.$phaseShift(LifePhase.READY, true)
174
+ }
175
+ })
176
+
177
+ // 6. 当前节点标记为 READY
178
+ lifecycleInvoking(this, 'ready')
179
+ }
180
+ ```
181
+
182
+ #### 步骤 6: 添加子节点 ($appendChild)
183
+
184
+ ```typescript
185
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/com.ts:1310-1335
186
+ $appendChild(child: Com) {
187
+ // 1. 如果存在旧关系,先解除
188
+ const lastPCom = child.pCom
189
+ if (lastPCom) {
190
+ lastPCom.$removeChild(child)
191
+ }
192
+
193
+ // 2. 设计态下创建布局事件
194
+ if (InnerApi.isDesignMode()) {
195
+ InnerApi.autoCreateComTreeLayoutEvent(this)
196
+ }
197
+
198
+ // 3. 建立父子关系
199
+ child.pCom = this
200
+ this.children.push(child)
201
+ this.$$linkComCtx(child)
202
+
203
+ // 4. 重新生成 orderNo
204
+ this.children.forEach((com, index) => {
205
+ com.orderNo = index
206
+ })
207
+
208
+ // 5. 标记子节点为 ATTACHED 状态
209
+ child.$phaseShift(LifePhase.ATTACHED, true)
210
+ }
211
+ ```
212
+
213
+ #### 步骤 7: Com 实例化过程
214
+
215
+ 当创建 Com 实例时,会执行以下初始化:
216
+
217
+ ```typescript
218
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/com.ts:264-297
219
+ constructor(options: ComArgs) {
220
+ super(options)
221
+
222
+ // 1. 存储原始数据(向后兼容)
223
+ this[STATE].data = options
224
+
225
+ // 2. 初始化布局
226
+ this[STATE].iRegion = options.regions || this.defaultLayout()
227
+ this.initLayout()
228
+
229
+ // 3. 创建表达式对象
230
+ this[BOUNDED].expression = new Expression({ comKId: this.keyIdentifier })
231
+
232
+ // 4. 生成 UI Props
233
+ this.uiProps = this.generateUiProps()
234
+
235
+ // 5. 内存监控(开发环境)
236
+ if (__watch_memory) {
237
+ incrCmpCount(this.cmpType)
238
+ fRegistry?.register(this, this.cmpType)
239
+ }
240
+
241
+ // 6. 异步创建 Store
242
+ queueMicrotask(() => {
243
+ this.$tryCreateStore()
244
+ })
245
+ }
246
+ ```
247
+
248
+ ## 四、ComFinder 组件查找机制
249
+
250
+ ### 4.1 Com 注册
251
+
252
+ 组件通过 `@registerCom` 装饰器注册:
253
+
254
+ ```typescript
255
+ // packages/neo-ui-common/src/_comTree/kernel/register/register.com.ts:174-191
256
+ function registerCom(com: typeof Com) {
257
+ let { _cmpType, _uiType } = com
258
+ if (!_cmpType && !_uiType) {
259
+ console.warn('register com failed, com must has both static properties: _cmpType and _uiType')
260
+ return
261
+ }
262
+
263
+ // 应用装饰器
264
+ ComDecorator(com)
265
+
266
+ // 建立映射关系
267
+ CmpTypePair.setPair(_cmpType, _uiType)
268
+
269
+ // 注册到全局列表
270
+ COM_DEFS.unshift({
271
+ cmpType: _cmpType,
272
+ uiType: _uiType,
273
+ comClass: com
274
+ })
275
+ }
276
+ ```
277
+
278
+ ### 4.2 Com 查找
279
+
280
+ ```typescript
281
+ // 根据 cmpType 查找对应的 Com 类
282
+ let ComClass = ComFinder.getCom({ cmpType: cmp.cmpType })
283
+ ```
284
+
285
+ ## 五、布局初始化
286
+
287
+ ### 5.1 布局结构
288
+
289
+ Com 可以包含布局信息(regions),布局定义了子组件的排列方式:
290
+
291
+ ```typescript
292
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/com.ts:193-213
293
+ protected initLayout() {
294
+ let regions = this.regions as RegionSchema | undefined
295
+ if (regions) {
296
+ const { template, slots } = regions
297
+ if (!template) {
298
+ console.error('template is required', this)
299
+ return
300
+ }
301
+
302
+ // 根据模板名称查找布局类
303
+ const LayoutClass = XLayout.getLayout(template)
304
+ if (!LayoutClass) {
305
+ throw new Error(`Layout template ${regions?.template} not found`)
306
+ }
307
+
308
+ // 创建布局实例
309
+ const layout = new LayoutClass(template)
310
+ layout.existAsRegions(regions, this)
311
+ if (slots) {
312
+ layout.syncSlots(slots)
313
+ }
314
+
315
+ this[SCOPE].layout = layout
316
+ this[BOUNDED].boundLayout = layout
317
+ }
318
+ }
319
+ ```
320
+
321
+ ### 5.2 布局类型
322
+
323
+ - **SlotLayout**: 插槽布局,支持多个插槽(如 header, body, footer)
324
+ - **FlexLayout**: 弹性布局
325
+ - **GridLayout**: 网格布局
326
+
327
+ ## 六、扩展机制
328
+
329
+ ### 6.1 AgentCom 扩展
330
+
331
+ 在 ComTree 构建完成后,会应用扩展:
332
+
333
+ ```typescript
334
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/pageCom/formPageUniCom.ts:237
335
+ this.applyAgentCom()
336
+ ```
337
+
338
+ 扩展可以:
339
+ - 修改组件的属性(uiProps)
340
+ - 扩展组件的方法(通过 AOP)
341
+ - 添加新的子组件
342
+
343
+ ### 6.2 扩展优先级
344
+
345
+ ```typescript
346
+ enum DEV_PRIORITY {
347
+ SYSTEM = 1, // 系统级
348
+ ISV = 2, // ISV 扩展
349
+ CUSTOM = 3 // 自定义扩展
350
+ }
351
+ ```
352
+
353
+ ## 七、变量空间
354
+
355
+ ### 7.1 变量空间初始化
356
+
357
+ 页面级别的变量空间在 `setupVarSpace` 中初始化:
358
+
359
+ ```typescript
360
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/pageCom/formPageUniCom.ts:70-103
361
+ setupVarSpace() {
362
+ let masterCom = this.getMasterFormCom()
363
+ let mainProps = masterCom.getProps()
364
+
365
+ // 创建页面变量空间
366
+ const pageVs = new VarSpace({
367
+ key: '$page',
368
+ label: '页面属性',
369
+ observable: false,
370
+ writable: false
371
+ })
372
+
373
+ // 添加页面属性
374
+ pageVs.$appendLeaf('objectApiKey', { ... })
375
+ pageVs.$appendLeaf('recordId', { ... })
376
+ // ...
377
+
378
+ // 绑定到页面
379
+ VarSpaceInTree.bindPageVs(this, pageVs)
380
+ }
381
+ ```
382
+
383
+ ### 7.2 变量作用域
384
+
385
+ - **$page**: 页面级变量
386
+ - **$mainForm**: 主表单变量
387
+ - **$subForm**: 子表单变量
388
+ - **$global**: 全局变量
389
+
390
+ ## 八、渲染准备
391
+
392
+ ### 8.1 ready2render 标志
393
+
394
+ 当 ComTree 构建完成并准备好渲染时,会设置 `ready2render = true`:
395
+
396
+ ```typescript
397
+ // packages/neo-ui-common/src/_comTreeDom/baseCmp/basePage.tsx:55-58
398
+ __com__.on('ready', () => {
399
+ this.onReady2Render(__com__)
400
+ __com__.enableRenderState() // 设置 ready2render = true
401
+ })
402
+ ```
403
+
404
+ ### 8.2 Schema 生成
405
+
406
+ ComTree 构建完成后,通过 `getJsonSchema` 生成 amis Schema:
407
+
408
+ ```typescript
409
+ // packages/neo-ui-common/src/_comTree/standard.coms/com/pageCom.ts:210-227
410
+ getJsonSchema() {
411
+ let topLayoutCom = this.getRootLayoutCom()
412
+ let schema: any = {}
413
+ if (topLayoutCom) {
414
+ schema = topLayoutCom.getJsonSchema()
415
+ } else {
416
+ schema = {
417
+ body: '页面配置错误,没有配置布局',
418
+ type: 'wrapper',
419
+ keyIdentifier: this.keyIdentifier,
420
+ cmpType: 'wrapper',
421
+ __com__: this
422
+ }
423
+ }
424
+ return schema
425
+ }
426
+ ```
427
+
428
+ ## 九、关键数据结构
429
+
430
+ ### 9.1 ComArgs
431
+
432
+ ```typescript
433
+ type ComArgs<T = any> = {
434
+ keyIdentifier: string // 唯一标识
435
+ cmpType: string // 组件类型
436
+ props?: T // 组件属性
437
+ regions?: RegionSchema // 布局信息
438
+ subCmps?: ComArgs[] // 子组件
439
+ orderNo?: number // 排序号
440
+ // ...
441
+ }
442
+ ```
443
+
444
+ ### 9.2 RegionSchema
445
+
446
+ ```typescript
447
+ type RegionSchema = {
448
+ template: string // 布局模板名称
449
+ slots?: { // 插槽定义
450
+ [slotName: string]: SlotArgs
451
+ }
452
+ cells?: object // 单元格配置
453
+ config?: object // 布局配置
454
+ }
455
+ ```
456
+
457
+ ## 十、总结
458
+
459
+ ComTree 的生成是一个递归的过程:
460
+
461
+ 1. **入口**: 页面打开时创建 PageCom
462
+ 2. **数据加载**: Store 加载布局数据
463
+ 3. **树构建**: 通过 `$expandSubTree` 递归创建子节点
464
+ 4. **扩展应用**: 应用 AgentCom 扩展
465
+ 5. **准备渲染**: 设置变量空间,标记 ready2render
466
+ 6. **Schema 生成**: 生成 amis Schema 用于渲染
467
+
468
+ 整个过程是异步的,通过 Promise 和生命周期钩子来协调各个阶段的执行顺序。
469
+
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * BaseCmpProps 基础组件属性接口
5
+ */
6
+ export interface BaseCmpProps {
7
+ [key: string]: any;
8
+ }
9
+
10
+ /**
11
+ * ScopedComponentType 作用域组件类型接口
12
+ */
13
+ export interface ScopedComponentType {
14
+ [key: string]: any;
15
+ }
16
+
17
+ /**
18
+ * BaseCmp 基础组件类
19
+ * 继承自 React.PureComponent,提供基础组件功能
20
+ */
21
+ export declare class BaseCmp<
22
+ T extends BaseCmpProps = BaseCmpProps,
23
+ S = any
24
+ > extends React.PureComponent<T, S> implements ScopedComponentType {
25
+ props: Readonly<T> & Readonly<{ children?: React.ReactNode }>;
26
+ state: Readonly<S>;
27
+ setState<K extends keyof S>(
28
+ state:
29
+ | ((prevState: Readonly<S>, props: Readonly<T>) => Pick<S, K> | S | null)
30
+ | (Pick<S, K> | S | null),
31
+ callback?: () => void
32
+ ): void;
33
+ forceUpdate(callback?: () => void): void;
34
+ render(): React.ReactNode;
35
+ }
36
+
@@ -2,7 +2,12 @@ import * as React from 'react';
2
2
  import { Card, Row, Col, Spin, Empty, Avatar, Button } from 'antd';
3
3
  import { UserOutlined, PhoneOutlined, ReloadOutlined } from '@ant-design/icons';
4
4
  // @ts-ignore
5
- import { xObject } from 'neo-open-api'; // Neo Open API
5
+ import { xObject } from 'neo-open-api';
6
+
7
+ // Neo Open API// 引入 neo-ui-common / BaseCmp
8
+ // @ts-ignore
9
+ import { BaseCmp } from 'neo-ui-common';
10
+
6
11
  import './style.scss';
7
12
 
8
13
  interface EntityCardListProps {
@@ -26,7 +31,7 @@ interface EntityCardListState {
26
31
  error: string | null;
27
32
  }
28
33
 
29
- export default class EntityCardList extends React.PureComponent<
34
+ export default class EntityCardList extends BaseCmp<
30
35
  EntityCardListProps,
31
36
  EntityCardListState
32
37
  > {
@@ -47,7 +52,7 @@ export default class EntityCardList extends React.PureComponent<
47
52
  this.loadObjectData();
48
53
  }
49
54
 
50
- componentDidUpdate(prevProps: EntityCardListProps) {
55
+ componentDidUpdate(prevProps: Readonly<EntityCardListProps>) {
51
56
  const { xObjectDataApi } = this.props;
52
57
  if (
53
58
  xObjectDataApi?.xObjectApiKey !==
@@ -58,10 +63,13 @@ export default class EntityCardList extends React.PureComponent<
58
63
  }
59
64
  }
60
65
 
61
- async loadObjectData() {
66
+ async loadObjectData(parma1?: any) {
62
67
  const { xObjectDataApi } = this.props;
63
68
  const { autoFetchData } = xObjectDataApi || {};
64
69
 
70
+ // 测试输出,输出当前参数
71
+ console.log('parma1:', parma1);
72
+
65
73
  if (autoFetchData) {
66
74
  // 方式一:直接从 props 中取实体数据源相关数据
67
75
  const { entityData: entityDataList } = this.props;
@@ -189,7 +197,7 @@ export default class EntityCardList extends React.PureComponent<
189
197
  />
190
198
  ) : (
191
199
  <Row gutter={[16, 16]}>
192
- {objectDataList.map((object, index) =>
200
+ {objectDataList.map((object: ContactData, index: number) =>
193
201
  this.renderEntityCard(object, index),
194
202
  )}
195
203
  </Row>
@@ -50,7 +50,6 @@ export class EntityCardListModel {
50
50
  apiKey: 'loadObjectData',
51
51
  label: '重新获取数据列表',
52
52
  helpTextKey: '获取实体业务数据列表',
53
- funcInParams: [],
54
53
  },
55
54
  ];
56
55
 
@@ -27,9 +27,9 @@ import {
27
27
  import { xObject } from 'neo-open-api'; // Neo Open API
28
28
  // @ts-ignore
29
29
  import isEqual from 'lodash/isEqual';
30
- // 引入 neo-ui-common / aop
30
+ // 引入 neo-ui-common / NeoEvent
31
31
  // @ts-ignore
32
- import { aop } from 'neo-ui-common'; // 后续考虑 使用 props.dispatchEvent 方法替代
32
+ import { NeoEvent } from 'neo-ui-common'; // 后续考虑 使用 props.dispatchEvent 方法替代
33
33
  import './style.scss';
34
34
 
35
35
  const { Option } = Select;
@@ -233,7 +233,7 @@ export default class EntityForm extends React.PureComponent<
233
233
  /**
234
234
  * 表单提交事件
235
235
  */
236
- @aop
236
+ @NeoEvent.dispatch
237
237
  onSubmit() {
238
238
  console.log('触发了表单提交事件:', this.props);
239
239
  }
@@ -58,10 +58,9 @@ export class EntityFormModel {
58
58
  events = [
59
59
  {
60
60
  apiKey: 'onSubmit', // 事件名称
61
- // description: '这是一个表单提交事件', // 暂未使用
62
61
  label: '提交表单后', // 事件
63
- helpText: '表单提交后触发该事件' // 信息icon hover 时的提示文本
64
- }
62
+ helpText: '表单提交后触发该事件', // 信息icon hover 时的提示文本
63
+ },
65
64
  ];
66
65
 
67
66
  /**
@@ -33,6 +33,11 @@ import moment from 'moment';
33
33
  import { xObject } from 'neo-open-api'; // Neo Open API
34
34
  // @ts-ignore
35
35
  import isEqual from 'lodash/isEqual';
36
+
37
+ // Neo Open API// 引入 neo-ui-common / BaseCmp
38
+ // @ts-ignore
39
+ import { BaseCmp } from 'neo-ui-common';
40
+
36
41
  import './style.scss';
37
42
 
38
43
  const { Option } = Select;
@@ -120,7 +125,7 @@ interface EntityTableState {
120
125
  * XObject 数据表格组件
121
126
  * 支持对 Neo 平台的 XObject 实体对象进行增删改查操作
122
127
  */
123
- export default class EntityTable extends React.PureComponent<
128
+ export default class EntityTable extends BaseCmp<
124
129
  EntityTableProps,
125
130
  EntityTableState
126
131
  > {
@@ -50,6 +50,28 @@ export class EntityTableModel {
50
50
  },
51
51
  };
52
52
 
53
+ // 当前组件支持的函数列表(其他组件可触发当前组件的函数)
54
+ functions = [
55
+ {
56
+ apiKey: 'loadData',
57
+ label: '刷新表格',
58
+ helpTextKey: '刷新当前表格数据',
59
+ },
60
+ {
61
+ apiKey: 'handleDelete',
62
+ label: '删除记录',
63
+ helpTextKey: '删除表格中指定记录',
64
+ funcInParams: [
65
+ {
66
+ apiKey: 'id',
67
+ label: '记录ID',
68
+ type: 'String',
69
+ required: true,
70
+ },
71
+ ],
72
+ },
73
+ ];
74
+
53
75
  /**
54
76
  * 组件属性配置模式
55
77
  * 支持静态配置:propsSchema,优先级比 propsSchemaCreator 低