@kkarum/framework 2.3.17 → 2.3.19
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 +167 -93
- package/docs/CODEX_GUIDE.md +151 -0
- package/docs/CODEX_GUIDE.md.meta +6 -0
- package/docs/ERROR_HANDLING.md +233 -0
- package/docs/ERROR_HANDLING.md.meta +6 -0
- package/docs/FRAMEWORK_API.md +414 -0
- package/docs/FRAMEWORK_API.md.meta +6 -0
- package/docs/FRAMEWORK_EXAMPLES.md +309 -0
- package/docs/FRAMEWORK_EXAMPLES.md.meta +6 -0
- package/docs/FRAMEWORK_OVERVIEW.md +127 -0
- package/docs/FRAMEWORK_OVERVIEW.md.meta +6 -0
- package/docs/FRAMEWORK_PATTERNS.md +261 -0
- package/docs/FRAMEWORK_PATTERNS.md.meta +6 -0
- package/docs/FRAMEWORK_USAGE.md +309 -0
- package/docs/FRAMEWORK_USAGE.md.meta +6 -0
- package/docs/MCP_INTEGRATION.md +241 -0
- package/docs/MCP_INTEGRATION.md.meta +6 -0
- package/docs/UI_DEVELOPMENT_GUIDE.md +260 -0
- package/docs/UI_DEVELOPMENT_GUIDE.md.meta +6 -0
- package/docs.meta +13 -0
- package/package.json +1 -1
- package/service/http/FWHttp.ts +80 -1
- package/types/FW.d.ts +32 -18
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# MCP Integration
|
|
2
|
+
|
|
3
|
+
本文档说明如何配合 cocos-mcp 进行 UI 自动化开发,并让生成的 UI 与本框架协作。
|
|
4
|
+
|
|
5
|
+
## 适用范围
|
|
6
|
+
|
|
7
|
+
cocos-mcp 用于:
|
|
8
|
+
|
|
9
|
+
- 读取当前 Cocos 场景树。
|
|
10
|
+
- 创建 UI 节点树。
|
|
11
|
+
- 添加 Cocos 白名单组件。
|
|
12
|
+
- 绑定 SpriteFrame。
|
|
13
|
+
- 实例化 prefab。
|
|
14
|
+
- 创建 prefab 资源。
|
|
15
|
+
- 保存场景。
|
|
16
|
+
|
|
17
|
+
框架代码用于:
|
|
18
|
+
|
|
19
|
+
- 定义业务 LayerController。
|
|
20
|
+
- 管理 Layer 打开/关闭。
|
|
21
|
+
- 绑定运行时事件。
|
|
22
|
+
- 管理资源生命周期。
|
|
23
|
+
|
|
24
|
+
二者分工:MCP 负责编辑器里的节点和资源,框架负责运行时行为。
|
|
25
|
+
|
|
26
|
+
## 推荐流程
|
|
27
|
+
|
|
28
|
+
### 1. 诊断连接
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
cocos_mcp_diagnose
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
确认:
|
|
35
|
+
|
|
36
|
+
- WebSocket bridge reachable。
|
|
37
|
+
- 当前场景已打开。
|
|
38
|
+
- Canvas 存在。
|
|
39
|
+
- Cocos Creator 版本兼容。
|
|
40
|
+
|
|
41
|
+
### 2. 获取真实场景树
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
scene_get_tree({
|
|
45
|
+
includeDiagnostics: true,
|
|
46
|
+
maxDepth: 6
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
禁止猜测节点路径。后续 MCP 操作必须使用绝对路径,例如 `/Canvas/MainLayer/CloseButton`。
|
|
51
|
+
|
|
52
|
+
### 3. 查询或解析资源
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
asset_query({
|
|
56
|
+
pattern: "db://assets/**/*.png",
|
|
57
|
+
type: "sprite-frame"
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
或:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
asset_resolve({
|
|
65
|
+
path: "assets/shop/texture/icon_coin.png",
|
|
66
|
+
type: "sprite-frame"
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
规则:
|
|
71
|
+
|
|
72
|
+
- 资源缺失时不要继续绑定。
|
|
73
|
+
- 多个候选时不要随机选择。
|
|
74
|
+
- 使用 Sprite 时绑定 `sprite-frame`,不是 texture 原图。
|
|
75
|
+
|
|
76
|
+
### 4. 创建 UI 树
|
|
77
|
+
|
|
78
|
+
大结构使用 `ui_create_tree`:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"root": "/Canvas",
|
|
83
|
+
"tree": {
|
|
84
|
+
"name": "ShopLayer",
|
|
85
|
+
"props": { "width": 720, "height": 1280, "x": 0, "y": 0 },
|
|
86
|
+
"components": [
|
|
87
|
+
{ "type": "cc.Widget", "props": { "isAlignTop": true, "isAlignBottom": true, "isAlignLeft": true, "isAlignRight": true } }
|
|
88
|
+
],
|
|
89
|
+
"children": [
|
|
90
|
+
{
|
|
91
|
+
"name": "CloseButton",
|
|
92
|
+
"props": { "width": 96, "height": 96, "x": 300, "y": 560 },
|
|
93
|
+
"components": [
|
|
94
|
+
{ "type": "cc.Button" },
|
|
95
|
+
{ "type": "cc.Sprite" }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
小改动使用:
|
|
104
|
+
|
|
105
|
+
- `ui_create_node`
|
|
106
|
+
- `ui_set_node_props`
|
|
107
|
+
- `ui_add_component`
|
|
108
|
+
- `ui_set_component_props`
|
|
109
|
+
- `ui_apply_widget`
|
|
110
|
+
- `ui_apply_layout`
|
|
111
|
+
|
|
112
|
+
### 5. 绑定图片
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
ui_set_sprite_frame({
|
|
116
|
+
nodePath: "/Canvas/ShopLayer/CloseButton",
|
|
117
|
+
asset: {
|
|
118
|
+
url: "db://assets/shop/texture/close.png",
|
|
119
|
+
type: "sprite-frame"
|
|
120
|
+
},
|
|
121
|
+
addComponentIfMissing: true
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 6. 生成 prefab
|
|
126
|
+
|
|
127
|
+
如果 UI 是运行时 Layer,推荐保存为 prefab:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
prefab_create_from_node({
|
|
131
|
+
nodePath: "/Canvas/ShopLayer",
|
|
132
|
+
url: "db://assets/shop/prefab/ShopLayer.prefab",
|
|
133
|
+
overwrite: true
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
然后在 `ShopAssetConfig` 中声明:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
ShopLayer: {
|
|
141
|
+
bundle: "shop",
|
|
142
|
+
path: "prefab/ShopLayer",
|
|
143
|
+
type: cc.Prefab,
|
|
144
|
+
priorityLoaded: true,
|
|
145
|
+
autoRelease: true,
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 7. 运行时代码绑定事件
|
|
150
|
+
|
|
151
|
+
优先在 `LayerController` 中绑定:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
onInit() {
|
|
155
|
+
this.cc(this.find("CloseButton", this.layer.node), this.onCloseClick);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
只有当目标节点上已经有脚本组件和 handler 时,才使用 MCP 的 `ui_bind_button_event`。
|
|
160
|
+
|
|
161
|
+
## Layer prefab 约定
|
|
162
|
+
|
|
163
|
+
用于 `FW.Entry.layerMgr` 的 prefab 应满足:
|
|
164
|
+
|
|
165
|
+
- 根节点名和资源名清晰对应,例如 `ShopLayer`。
|
|
166
|
+
- 根节点挂载 `FW.Layer` 或业务继承的 Layer 组件。
|
|
167
|
+
- 控制器类不挂在节点上,由 `FWLayerManager` 创建并绑定到 `layer.ctr`。
|
|
168
|
+
- prefab 资源路径与 `AssetConfig` 一致。
|
|
169
|
+
- 需要全屏适配时根节点使用 `cc.Widget` full/stretch。
|
|
170
|
+
|
|
171
|
+
## UI 节点命名约定
|
|
172
|
+
|
|
173
|
+
推荐:
|
|
174
|
+
|
|
175
|
+
- `CloseButton`
|
|
176
|
+
- `ConfirmButton`
|
|
177
|
+
- `CancelButton`
|
|
178
|
+
- `TitleLabel`
|
|
179
|
+
- `GoodsScrollView`
|
|
180
|
+
- `Content`
|
|
181
|
+
- `ItemTemplate`
|
|
182
|
+
- `IconSprite`
|
|
183
|
+
|
|
184
|
+
避免:
|
|
185
|
+
|
|
186
|
+
- `node1`
|
|
187
|
+
- `btn`
|
|
188
|
+
- 中文节点名
|
|
189
|
+
- 与多个兄弟节点重复的名字
|
|
190
|
+
|
|
191
|
+
原因:`LayerController.find()` 和装饰器依赖节点名查找。重复或含义不明的节点会降低自动化可靠性。
|
|
192
|
+
|
|
193
|
+
## Codex + MCP 协作模板
|
|
194
|
+
|
|
195
|
+
当用户要求“创建一个商店弹窗”:
|
|
196
|
+
|
|
197
|
+
1. 用 MCP 读取场景树,确认 `/Canvas`。
|
|
198
|
+
2. 用 MCP 创建 `ShopLayer` 节点树。
|
|
199
|
+
3. 查询资源并绑定 SpriteFrame。
|
|
200
|
+
4. 保存为 `assets/shop/prefab/ShopLayer.prefab`。
|
|
201
|
+
5. 在业务代码新增 `ShopLayerController`。
|
|
202
|
+
6. 在 `ShopAssetConfig` 声明 prefab。
|
|
203
|
+
7. 用 `this.cc()` 绑定 `CloseButton`、`BuyButton`。
|
|
204
|
+
8. 用 `FW.Entry.layerMgr.openAsync({ type: ShopLayerController })` 打开。
|
|
205
|
+
|
|
206
|
+
## 干运行和验证
|
|
207
|
+
|
|
208
|
+
优先使用 `dryRun: true` 验证高风险操作:
|
|
209
|
+
|
|
210
|
+
- 删除节点。
|
|
211
|
+
- 覆盖 prefab。
|
|
212
|
+
- 批量创建大树。
|
|
213
|
+
- 绑定不确定资源。
|
|
214
|
+
|
|
215
|
+
完成后验证:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
scene_get_tree({ includeDiagnostics: true, maxDepth: 8 })
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
检查:
|
|
222
|
+
|
|
223
|
+
- 节点路径存在。
|
|
224
|
+
- 组件存在。
|
|
225
|
+
- SpriteFrame 已绑定。
|
|
226
|
+
- prefab 已生成。
|
|
227
|
+
- 没有多余测试节点遗留。
|
|
228
|
+
|
|
229
|
+
## 保存策略
|
|
230
|
+
|
|
231
|
+
不要在用户未确认时随意保存场景。以下情况可保存:
|
|
232
|
+
|
|
233
|
+
- 用户明确要求保存。
|
|
234
|
+
- 当前任务目标就是生成 prefab 或落地 UI。
|
|
235
|
+
- 已完成验证且保存是任务必要结果。
|
|
236
|
+
|
|
237
|
+
保存使用:
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
scene_save()
|
|
241
|
+
```
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# UI Development Guide
|
|
2
|
+
|
|
3
|
+
本文档定义基于本框架开发 UI 的标准做法。
|
|
4
|
+
|
|
5
|
+
## UI 类型
|
|
6
|
+
|
|
7
|
+
### 常驻层
|
|
8
|
+
|
|
9
|
+
适合主界面、HUD、底部导航。
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
layerType = FW.SystemDefine.FWLayerType.PERMANENT;
|
|
13
|
+
renderOrder = FW.SystemDefine.FWLayerRenderOrder.PERMANENT;
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
可使用:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
this.hide();
|
|
20
|
+
this.display();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
框架会暂停/恢复节点系统事件、框架事件和定时器。
|
|
24
|
+
|
|
25
|
+
### 普通 UI
|
|
26
|
+
|
|
27
|
+
适合可重复打开的页面。
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
layerType = FW.SystemDefine.FWLayerType.REPEAT;
|
|
31
|
+
renderOrder = FW.SystemDefine.FWLayerRenderOrder.UI;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 队列弹窗
|
|
35
|
+
|
|
36
|
+
适合奖励弹窗、确认弹窗。
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
layerType = FW.SystemDefine.FWLayerType.POPUP_QUEUE;
|
|
40
|
+
renderOrder = FW.SystemDefine.FWLayerRenderOrder.POPUP;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
已有队列弹窗时,新弹窗会入队,当前弹窗关闭后再打开下一个。
|
|
44
|
+
|
|
45
|
+
### 一次性弹窗
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
layerType = FW.SystemDefine.FWLayerType.POPUP_DISPOSABLE;
|
|
49
|
+
renderOrder = FW.SystemDefine.FWLayerRenderOrder.POPUP;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## LayerController 标准结构
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
export class InventoryLayerController extends FW.LayerController {
|
|
56
|
+
renderOrder = FW.SystemDefine.FWLayerRenderOrder.UI;
|
|
57
|
+
layerType = FW.SystemDefine.FWLayerType.REPEAT;
|
|
58
|
+
autoRelease = true;
|
|
59
|
+
isRepeatOpen = false;
|
|
60
|
+
|
|
61
|
+
private closeButton: cc.Node;
|
|
62
|
+
private titleLabel: cc.Label;
|
|
63
|
+
|
|
64
|
+
get layerAssetProperty(): FW.AssetProperty {
|
|
65
|
+
return this.getConfig<InventoryAssetConfig>().preLoad.prefab["InventoryLayer"] as FW.AssetProperty;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onInit(args?: any) {
|
|
69
|
+
this.cacheNodes();
|
|
70
|
+
this.bindEvents();
|
|
71
|
+
this.render();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private cacheNodes() {
|
|
75
|
+
this.closeButton = this.find("CloseButton", this.layer.node);
|
|
76
|
+
this.titleLabel = this.findComponent("TitleLabel", this.layer.node, cc.Label);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private bindEvents() {
|
|
80
|
+
this.cc(this.closeButton, this.onCloseClick);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private render() {
|
|
84
|
+
this.titleLabel.string = "Inventory";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private onCloseClick = () => {
|
|
88
|
+
this.close();
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 节点查找
|
|
94
|
+
|
|
95
|
+
推荐:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const node = this.find("CloseButton", this.layer.node);
|
|
99
|
+
const label = this.findComponent("TitleLabel", this.layer.node, cc.Label);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
可使用装饰器:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
@FWPropertyNode({ path: "CloseButton" })
|
|
106
|
+
private closeButton: cc.Node;
|
|
107
|
+
|
|
108
|
+
@FWPropertyComponent(cc.Label, "TitleLabel")
|
|
109
|
+
private titleLabel: cc.Label;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
注意:
|
|
113
|
+
|
|
114
|
+
- 装饰器改写 `onLoad`,更适合 Cocos Component。
|
|
115
|
+
- LayerController 不是挂在节点上的 Cocos Component,优先使用 `find()`。
|
|
116
|
+
|
|
117
|
+
## 事件绑定
|
|
118
|
+
|
|
119
|
+
按钮:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
this.cc(buttonNode, this.onButtonClick);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
自定义事件:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
this.fw("INVENTORY_CHANGED", this.render);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
复杂:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
this.registerEvent({
|
|
135
|
+
CCEvent: [{
|
|
136
|
+
target: buttonNode,
|
|
137
|
+
eventName: cc.Node.EventType.TOUCH_END,
|
|
138
|
+
responseInterval: 500,
|
|
139
|
+
cb: this.onButtonClick,
|
|
140
|
+
}],
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
不要在 LayerController 中散落裸 `node.on()`。如必须使用,必须在关闭时手动 `off`。
|
|
145
|
+
|
|
146
|
+
## 列表开发
|
|
147
|
+
|
|
148
|
+
### 小列表
|
|
149
|
+
|
|
150
|
+
可以直接实例化 item prefab,但仍应使用 `FW.Entry.resMgr` 加载资源。
|
|
151
|
+
|
|
152
|
+
### 大列表
|
|
153
|
+
|
|
154
|
+
优先使用:
|
|
155
|
+
|
|
156
|
+
- `FWVirtualViewComponent`
|
|
157
|
+
- `FWVirtuaScrollViewComponent`
|
|
158
|
+
- `FW.Entry.objectMgr.createObjectPool()`
|
|
159
|
+
|
|
160
|
+
`FWVirtualViewComponent` 常用字段:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
list.onRender = (item, index) => {};
|
|
164
|
+
list.onSelected = (item, index, lastIndex, selected?) => {};
|
|
165
|
+
list.onPageChange = (item, page) => {};
|
|
166
|
+
list.count = data.length;
|
|
167
|
+
list.updateItem(index);
|
|
168
|
+
list.updateAll();
|
|
169
|
+
list.scrollTo(index, 0.3);
|
|
170
|
+
list.prePage();
|
|
171
|
+
list.nextPage();
|
|
172
|
+
list.skipPage(page, 0.3);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## UI 与 Data 同步
|
|
176
|
+
|
|
177
|
+
推荐方向:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
Logic -> Data -> evtMgr.dispatch -> LayerController.render
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
示例:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// Data
|
|
187
|
+
setItems(items: Item[]) {
|
|
188
|
+
this.items = items;
|
|
189
|
+
FW.Entry.evtMgr.dispatch("INVENTORY_ITEMS_CHANGED", items);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// LayerController
|
|
193
|
+
this.fw("INVENTORY_ITEMS_CHANGED", this.renderItems);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 打开和关闭
|
|
197
|
+
|
|
198
|
+
打开:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
await FW.Entry.layerMgr.openAsync<InventoryLayerController>({
|
|
202
|
+
type: InventoryLayerController,
|
|
203
|
+
args: { selectedTab: 1 },
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
关闭:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
this.close();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
关闭前异步处理:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
async onClose() {
|
|
217
|
+
await this.playCloseAnimation();
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`FWLayerManager.close()` 会等待 `onClose()`,再销毁节点、释放资源、清理状态。
|
|
222
|
+
|
|
223
|
+
## 资源释放
|
|
224
|
+
|
|
225
|
+
LayerController 属性:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
autoRelease = true;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
关闭时,如果 `autoRelease` 为 true,框架会调用:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
FW.Entry.resMgr.releaseAsset(ctr.layerData.layerAssetProperty);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
注意:资源释放依赖 `AssetProperty.autoRelease` 和引用计数。不要手动释放还在使用的公共资源。
|
|
238
|
+
|
|
239
|
+
## UI 自动化约定
|
|
240
|
+
|
|
241
|
+
使用 cocos-mcp 创建 UI 时:
|
|
242
|
+
|
|
243
|
+
- 根节点名与 prefab 名一致。
|
|
244
|
+
- 交互节点名与代码一致。
|
|
245
|
+
- 按钮节点含 `cc.Button` 和可视组件。
|
|
246
|
+
- 文本节点含 `cc.Label`。
|
|
247
|
+
- 滚动区域含 `cc.ScrollView`、`content`、`cc.Layout`。
|
|
248
|
+
- 全屏根节点使用 `cc.Widget` stretch/full。
|
|
249
|
+
|
|
250
|
+
## UI 质量检查
|
|
251
|
+
|
|
252
|
+
完成 UI 功能前检查:
|
|
253
|
+
|
|
254
|
+
- `layerAssetProperty` 能解析到有效 prefab。
|
|
255
|
+
- `onInit()` 中所有 `find()` 节点存在。
|
|
256
|
+
- 按钮不会重复注册。
|
|
257
|
+
- 列表刷新不会无限实例化节点。
|
|
258
|
+
- 关闭时没有未清理定时器。
|
|
259
|
+
- 弹窗类型和渲染顺序符合用途。
|
|
260
|
+
- `autoRelease` 与资源复用场景一致。
|
package/docs.meta
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ver": "1.1.3",
|
|
3
|
+
"uuid": "1e0c55a2-93ba-4368-9b54-26c8d470d0ed",
|
|
4
|
+
"importer": "folder",
|
|
5
|
+
"isBundle": false,
|
|
6
|
+
"bundleName": "",
|
|
7
|
+
"priority": 1,
|
|
8
|
+
"compressionType": {},
|
|
9
|
+
"optimizeHotUpdate": {},
|
|
10
|
+
"inlineSpriteFrames": {},
|
|
11
|
+
"isRemoteBundle": {},
|
|
12
|
+
"subMetas": {}
|
|
13
|
+
}
|
package/package.json
CHANGED
package/service/http/FWHttp.ts
CHANGED
|
@@ -34,7 +34,22 @@ export class FWHttp extends FW.Service {
|
|
|
34
34
|
FW.SystemDefine.FWHttpRequestType.POST,
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
protected async postApiMessage(
|
|
38
|
+
url: string,
|
|
39
|
+
params?: string,
|
|
40
|
+
headers?: string,
|
|
41
|
+
cb?: (response: string) => void,
|
|
42
|
+
tag?: string,
|
|
43
|
+
): Promise<string> {
|
|
44
|
+
return this.processApi(
|
|
45
|
+
url,
|
|
46
|
+
params,
|
|
47
|
+
headers,
|
|
48
|
+
cb,
|
|
49
|
+
tag,
|
|
50
|
+
FW.SystemDefine.FWHttpRequestType.POST,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
38
53
|
private async process(
|
|
39
54
|
url: string,
|
|
40
55
|
params: string,
|
|
@@ -97,7 +112,71 @@ export class FWHttp extends FW.Service {
|
|
|
97
112
|
tag ? `http request ->${tag}` : "",
|
|
98
113
|
);
|
|
99
114
|
}
|
|
115
|
+
private async processApi(
|
|
116
|
+
url: string,
|
|
117
|
+
params: string,
|
|
118
|
+
headers: string,
|
|
119
|
+
cb: (response: any) => void,
|
|
120
|
+
tag: string,
|
|
121
|
+
type: FW.SystemDefine.FWHttpRequestType,
|
|
122
|
+
): Promise<string> {
|
|
123
|
+
let xhr: XMLHttpRequest;
|
|
124
|
+
|
|
125
|
+
const promiseProxy: FW.PromiseProxy<string> =
|
|
126
|
+
FW.Entry.promiseMgr.execute<string>(
|
|
127
|
+
(resolve, reject, signal, reason) => {
|
|
128
|
+
xhr = new XMLHttpRequest();
|
|
129
|
+
|
|
130
|
+
xhr.onreadystatechange = () => {
|
|
131
|
+
if (xhr.readyState !== 4) return;
|
|
100
132
|
|
|
133
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
134
|
+
cb?.(xhr.response);
|
|
135
|
+
resolve(xhr.response);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const errMsg = `http request failed, status: ${xhr.status}, url: ${url}`;
|
|
140
|
+
FW.Log.warn(errMsg);
|
|
141
|
+
reject(errMsg);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
xhr.onerror = (err: any) => {
|
|
145
|
+
FW.Log.error("http request error:", err);
|
|
146
|
+
this.onError?.(err);
|
|
147
|
+
reject(`http request error:${err}`);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
xhr.ontimeout = () => {
|
|
151
|
+
this.onTimeout?.();
|
|
152
|
+
reject(`http request timeout!`);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
xhr.open(type as string, url, true);
|
|
156
|
+
xhr.setRequestHeader("Content-Type", "application/json");
|
|
157
|
+
xhr.setRequestHeader("sign", headers);
|
|
158
|
+
xhr.send(params);
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
...FWSystemConfig.PromiseConfig.http,
|
|
162
|
+
retryCondition(error, retryCount) {
|
|
163
|
+
return (
|
|
164
|
+
!!error &&
|
|
165
|
+
retryCount < (FWSystemConfig.PromiseConfig.http.retryCount || 0)
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
promiseProxy.addAbortEventListener(() => {
|
|
172
|
+
xhr.abort();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return await this.invoke(
|
|
176
|
+
promiseProxy.promise,
|
|
177
|
+
tag ? `http request ->${tag}` : "",
|
|
178
|
+
);
|
|
179
|
+
}
|
|
101
180
|
onError?(err: any);
|
|
102
181
|
onTimeout?();
|
|
103
182
|
}
|