@creatoria/miniapp-mcp 0.1.3 → 0.2.0
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 +14 -3
- package/dist/app/cli/index.d.ts +6 -0
- package/dist/app/cli/index.d.ts.map +1 -0
- package/dist/app/cli/index.js +6 -0
- package/dist/app/cli/index.js.map +1 -0
- package/dist/app/index.d.ts +6 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +6 -0
- package/dist/app/index.js.map +1 -0
- package/dist/app/server/index.d.ts +7 -0
- package/dist/app/server/index.d.ts.map +1 -0
- package/dist/app/server/index.js +6 -0
- package/dist/app/server/index.js.map +1 -0
- package/dist/capabilities/assert/index.d.ts +5 -0
- package/dist/capabilities/assert/index.d.ts.map +1 -0
- package/dist/capabilities/assert/index.js +5 -0
- package/dist/capabilities/assert/index.js.map +1 -0
- package/dist/capabilities/automator/index.d.ts +6 -0
- package/dist/capabilities/automator/index.d.ts.map +1 -0
- package/dist/capabilities/automator/index.js +6 -0
- package/dist/capabilities/automator/index.js.map +1 -0
- package/dist/capabilities/automator/schemas/close.d.ts +5 -0
- package/dist/capabilities/automator/schemas/close.d.ts.map +1 -0
- package/dist/capabilities/automator/schemas/close.js +11 -0
- package/dist/capabilities/automator/schemas/close.js.map +1 -0
- package/dist/capabilities/automator/schemas/connect.d.ts +11 -0
- package/dist/capabilities/automator/schemas/connect.d.ts.map +1 -0
- package/dist/capabilities/automator/schemas/connect.js +19 -0
- package/dist/capabilities/automator/schemas/connect.js.map +1 -0
- package/dist/capabilities/automator/schemas/disconnect.d.ts +5 -0
- package/dist/capabilities/automator/schemas/disconnect.d.ts.map +1 -0
- package/dist/capabilities/automator/schemas/disconnect.js +11 -0
- package/dist/capabilities/automator/schemas/disconnect.js.map +1 -0
- package/dist/capabilities/automator/schemas/index.d.ts +4 -0
- package/dist/capabilities/automator/schemas/index.d.ts.map +1 -0
- package/dist/capabilities/automator/schemas/index.js +12 -0
- package/dist/capabilities/automator/schemas/index.js.map +1 -0
- package/dist/capabilities/automator/schemas/launch.d.ts +17 -0
- package/dist/capabilities/automator/schemas/launch.d.ts.map +1 -0
- package/dist/capabilities/automator/schemas/launch.js +26 -0
- package/dist/capabilities/automator/schemas/launch.js.map +1 -0
- package/dist/capabilities/element/index.d.ts +5 -0
- package/dist/capabilities/element/index.d.ts.map +1 -0
- package/dist/capabilities/element/index.js +5 -0
- package/dist/capabilities/element/index.js.map +1 -0
- package/dist/capabilities/index.d.ts +15 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +14 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/miniprogram/index.d.ts +5 -0
- package/dist/capabilities/miniprogram/index.d.ts.map +1 -0
- package/dist/capabilities/miniprogram/index.js +5 -0
- package/dist/capabilities/miniprogram/index.js.map +1 -0
- package/dist/capabilities/network/index.d.ts +5 -0
- package/dist/capabilities/network/index.d.ts.map +1 -0
- package/dist/capabilities/network/index.js +5 -0
- package/dist/capabilities/network/index.js.map +1 -0
- package/dist/capabilities/page/index.d.ts +5 -0
- package/dist/capabilities/page/index.d.ts.map +1 -0
- package/dist/capabilities/page/index.js +5 -0
- package/dist/capabilities/page/index.js.map +1 -0
- package/dist/capabilities/record/index.d.ts +5 -0
- package/dist/capabilities/record/index.d.ts.map +1 -0
- package/dist/capabilities/record/index.js +5 -0
- package/dist/capabilities/record/index.js.map +1 -0
- package/dist/capabilities/schema-registry.d.ts +4 -0
- package/dist/capabilities/schema-registry.d.ts.map +1 -0
- package/dist/capabilities/schema-registry.js +18 -0
- package/dist/capabilities/schema-registry.js.map +1 -0
- package/dist/capabilities/schema-types.d.ts +22 -0
- package/dist/capabilities/schema-types.d.ts.map +1 -0
- package/dist/capabilities/schema-types.js +2 -0
- package/dist/capabilities/schema-types.js.map +1 -0
- package/dist/capabilities/snapshot/index.d.ts +5 -0
- package/dist/capabilities/snapshot/index.d.ts.map +1 -0
- package/dist/capabilities/snapshot/index.js +5 -0
- package/dist/capabilities/snapshot/index.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/core/element-ref.d.ts +1 -43
- package/dist/core/element-ref.d.ts.map +1 -1
- package/dist/core/element-ref.js +1 -212
- package/dist/core/element-ref.js.map +1 -1
- package/dist/core/logger.d.ts +1 -54
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +1 -377
- package/dist/core/logger.js.map +1 -1
- package/dist/core/output.d.ts +1 -20
- package/dist/core/output.d.ts.map +1 -1
- package/dist/core/output.js +1 -55
- package/dist/core/output.js.map +1 -1
- package/dist/core/report-generator.d.ts +1 -23
- package/dist/core/report-generator.d.ts.map +1 -1
- package/dist/core/report-generator.js +1 -211
- package/dist/core/report-generator.js.map +1 -1
- package/dist/core/session.d.ts +2 -82
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +2 -305
- package/dist/core/session.js.map +1 -1
- package/dist/core/timeout.d.ts +1 -48
- package/dist/core/timeout.d.ts.map +1 -1
- package/dist/core/timeout.js +1 -66
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/tool-logger.d.ts +1 -82
- package/dist/core/tool-logger.d.ts.map +1 -1
- package/dist/core/tool-logger.js +1 -452
- package/dist/core/tool-logger.js.map +1 -1
- package/dist/core/validation.d.ts +1 -38
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +1 -92
- package/dist/core/validation.js.map +1 -1
- package/dist/runtime/element/element-ref.d.ts +44 -0
- package/dist/runtime/element/element-ref.d.ts.map +1 -0
- package/dist/runtime/element/element-ref.js +214 -0
- package/dist/runtime/element/element-ref.js.map +1 -0
- package/dist/runtime/element/index.d.ts +2 -0
- package/dist/runtime/element/index.d.ts.map +1 -0
- package/dist/runtime/element/index.js +2 -0
- package/dist/runtime/element/index.js.map +1 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/logging/index.d.ts +3 -0
- package/dist/runtime/logging/index.d.ts.map +1 -0
- package/dist/runtime/logging/index.js +3 -0
- package/dist/runtime/logging/index.js.map +1 -0
- package/dist/runtime/logging/logger.d.ts +55 -0
- package/dist/runtime/logging/logger.d.ts.map +1 -0
- package/dist/runtime/logging/logger.js +379 -0
- package/dist/runtime/logging/logger.js.map +1 -0
- package/dist/runtime/logging/tool-logger.d.ts +83 -0
- package/dist/runtime/logging/tool-logger.d.ts.map +1 -0
- package/dist/runtime/logging/tool-logger.js +454 -0
- package/dist/runtime/logging/tool-logger.js.map +1 -0
- package/dist/runtime/outputs/index.d.ts +3 -0
- package/dist/runtime/outputs/index.d.ts.map +1 -0
- package/dist/runtime/outputs/index.js +3 -0
- package/dist/runtime/outputs/index.js.map +1 -0
- package/dist/runtime/outputs/output-manager.d.ts +12 -0
- package/dist/runtime/outputs/output-manager.d.ts.map +1 -0
- package/dist/runtime/outputs/output-manager.js +39 -0
- package/dist/runtime/outputs/output-manager.js.map +1 -0
- package/dist/runtime/outputs/report-generator.d.ts +5 -0
- package/dist/runtime/outputs/report-generator.d.ts.map +1 -0
- package/dist/runtime/outputs/report-generator.js +175 -0
- package/dist/runtime/outputs/report-generator.js.map +1 -0
- package/dist/runtime/session/index.d.ts +3 -0
- package/dist/runtime/session/index.d.ts.map +1 -0
- package/dist/runtime/session/index.js +3 -0
- package/dist/runtime/session/index.js.map +1 -0
- package/dist/runtime/session/store.d.ts +28 -0
- package/dist/runtime/session/store.d.ts.map +1 -0
- package/dist/runtime/session/store.js +154 -0
- package/dist/runtime/session/store.js.map +1 -0
- package/dist/runtime/session/utils/cleanup.d.ts +3 -0
- package/dist/runtime/session/utils/cleanup.d.ts.map +1 -0
- package/dist/runtime/session/utils/cleanup.js +78 -0
- package/dist/runtime/session/utils/cleanup.js.map +1 -0
- package/dist/runtime/timeout/index.d.ts +2 -0
- package/dist/runtime/timeout/index.d.ts.map +1 -0
- package/dist/runtime/timeout/index.js +2 -0
- package/dist/runtime/timeout/index.js.map +1 -0
- package/dist/runtime/timeout/timeout.d.ts +49 -0
- package/dist/runtime/timeout/timeout.d.ts.map +1 -0
- package/dist/runtime/timeout/timeout.js +67 -0
- package/dist/runtime/timeout/timeout.js.map +1 -0
- package/dist/runtime/validation/index.d.ts +2 -0
- package/dist/runtime/validation/index.d.ts.map +1 -0
- package/dist/runtime/validation/index.js +2 -0
- package/dist/runtime/validation/index.js.map +1 -0
- package/dist/runtime/validation/validation.d.ts +39 -0
- package/dist/runtime/validation/validation.d.ts.map +1 -0
- package/dist/runtime/validation/validation.js +93 -0
- package/dist/runtime/validation/validation.js.map +1 -0
- package/dist/schemas/automator/miniprogram_close.json +12 -0
- package/dist/schemas/automator/miniprogram_connect.json +19 -0
- package/dist/schemas/automator/miniprogram_disconnect.json +12 -0
- package/dist/schemas/automator/miniprogram_launch.json +30 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/miniprogram.d.ts +0 -1
- package/dist/tools/miniprogram.d.ts.map +1 -1
- package/dist/tools/miniprogram.js +17 -29
- package/dist/tools/miniprogram.js.map +1 -1
- package/dist/tools/page.js +2 -2
- package/dist/tools/page.js.map +1 -1
- package/docs/directory-structure-and-code-style-best-practices.md +91 -0
- package/docs/migration/README.md +34 -0
- package/docs/migration/TC-ALIGN-01-notes.md +35 -0
- package/docs/migration/runtime-skeleton.md +50 -0
- package/docs/migration/tool-schema-strategy.md +75 -0
- package/docs//345/256/214/346/225/264/345/256/236/347/216/260/346/226/271/346/241/210.md +14 -14
- package/docs//347/254/254/344/270/200/347/211/210/346/234/254/346/226/271/346/241/210.md +7 -7
- package/package.json +4 -2
- package/docs/SIMPLE_USAGE.md +0 -210
- package/docs/architecture.E-Docs.md +0 -1359
- package/docs/architecture.F1.md +0 -720
- package/docs/architecture.F2.md +0 -871
- package/docs/architecture.F3.md +0 -905
- package/docs/architecture.md +0 -90
- package/docs/charter.A1.align.yaml +0 -170
- package/docs/charter.A2.align.yaml +0 -199
- package/docs/charter.A3.align.yaml +0 -242
- package/docs/charter.A4.align.yaml +0 -227
- package/docs/charter.B1.align.yaml +0 -179
- package/docs/charter.B2.align.yaml +0 -200
- package/docs/charter.B3.align.yaml +0 -200
- package/docs/charter.B4.align.yaml +0 -188
- package/docs/charter.C1.align.yaml +0 -190
- package/docs/charter.C2.align.yaml +0 -202
- package/docs/charter.C3.align.yaml +0 -211
- package/docs/charter.C4.align.yaml +0 -263
- package/docs/charter.C5.align.yaml +0 -220
- package/docs/charter.D1.align.yaml +0 -190
- package/docs/charter.D2.align.yaml +0 -234
- package/docs/charter.D3.align.yaml +0 -206
- package/docs/charter.E-Docs.align.yaml +0 -294
- package/docs/charter.F1.align.yaml +0 -193
- package/docs/charter.F2.align.yaml +0 -248
- package/docs/charter.F3.align.yaml +0 -287
- package/docs/charter.G.align.yaml +0 -174
- package/docs/charter.align.yaml +0 -111
- package/docs/maintenance.md +0 -682
- package/docs/playwright-mcp/350/260/203/347/240/224.md +0 -53
- package/docs/setup-guide.md +0 -775
- package/docs/tasks.A1.atomize.md +0 -296
- package/docs/tasks.A2.atomize.md +0 -408
- package/docs/tasks.A3.atomize.md +0 -564
- package/docs/tasks.A4.atomize.md +0 -496
- package/docs/tasks.B1.atomize.md +0 -352
- package/docs/tasks.B2.atomize.md +0 -561
- package/docs/tasks.B3.atomize.md +0 -508
- package/docs/tasks.B4.atomize.md +0 -504
- package/docs/tasks.C1.atomize.md +0 -540
- package/docs/tasks.C2.atomize.md +0 -665
- package/docs/tasks.C3.atomize.md +0 -745
- package/docs/tasks.C4.atomize.md +0 -908
- package/docs/tasks.C5.atomize.md +0 -755
- package/docs/tasks.D1.atomize.md +0 -547
- package/docs/tasks.D2.atomize.md +0 -619
- package/docs/tasks.D3.atomize.md +0 -790
- package/docs/tasks.E-Docs.atomize.md +0 -1204
- package/docs/tasks.atomize.md +0 -189
package/docs/tasks.C3.atomize.md
DELETED
|
@@ -1,745 +0,0 @@
|
|
|
1
|
-
# Task Card: [C3] Page 工具
|
|
2
|
-
|
|
3
|
-
**Task ID**: C3
|
|
4
|
-
**Task Name**: Page 工具实现
|
|
5
|
-
**Charter**: `docs/charter.C3.align.yaml`
|
|
6
|
-
**Stage**: C (Tool Implementation)
|
|
7
|
-
**Status**: ✅ COMPLETED (Retrospective)
|
|
8
|
-
**Estimated**: 3-4 hours
|
|
9
|
-
**Actual**: ~4 hours
|
|
10
|
-
**Completed**: 2025-10-02
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 目标 (Goal)
|
|
15
|
-
|
|
16
|
-
实现 Page 级别的 8 个 MCP 工具,封装页面元素查询、等待、数据读写、方法调用和页面属性获取功能。
|
|
17
|
-
|
|
18
|
-
**交付物**:
|
|
19
|
-
- ✅ `src/tools/page.ts` (458 lines)
|
|
20
|
-
- ✅ `tests/unit/page.test.ts` (450 lines, 27 tests)
|
|
21
|
-
- ✅ 8 个工具: query, queryAll, waitFor, getData, setData, callMethod, getSize, getScrollTop
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 前置条件 (Prerequisites)
|
|
26
|
-
|
|
27
|
-
- ✅ C2: MiniProgram 工具已完成(导航功能)
|
|
28
|
-
- ✅ B2: SessionStore 已实现
|
|
29
|
-
- ✅ ElementRef 协议设计完成
|
|
30
|
-
- ✅ 了解 WXML 选择器语法
|
|
31
|
-
- ✅ 了解 XPath 查询语法
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 实现步骤 (Steps)
|
|
36
|
-
|
|
37
|
-
### 1. 定义 ElementRef Schema ✅
|
|
38
|
-
|
|
39
|
-
**文件**: `src/tools/page.ts`
|
|
40
|
-
|
|
41
|
-
**步骤**:
|
|
42
|
-
```typescript
|
|
43
|
-
import { z } from 'zod'
|
|
44
|
-
|
|
45
|
-
// ElementRef 协议
|
|
46
|
-
const elementRefSchema = z.object({
|
|
47
|
-
refId: z.string().optional().describe('缓存的元素引用 ID'),
|
|
48
|
-
selector: z.string().optional().describe('WXML 选择器'),
|
|
49
|
-
xpath: z.string().optional().describe('XPath 选择器'),
|
|
50
|
-
index: z.number().optional().describe('多元素索引'),
|
|
51
|
-
pagePath: z.string().optional().describe('目标页面路径'),
|
|
52
|
-
save: z.boolean().optional().describe('保存到缓存'),
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
// query 工具 Schema
|
|
56
|
-
const querySchema = z.object({
|
|
57
|
-
sessionId: z.string(),
|
|
58
|
-
elementRef: elementRefSchema,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// queryAll 工具 Schema
|
|
62
|
-
const queryAllSchema = z.object({
|
|
63
|
-
sessionId: z.string(),
|
|
64
|
-
selector: z.string().optional(),
|
|
65
|
-
xpath: z.string().optional(),
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// waitFor 工具 Schema
|
|
69
|
-
const waitForSchema = z.object({
|
|
70
|
-
sessionId: z.string(),
|
|
71
|
-
elementRef: elementRefSchema,
|
|
72
|
-
timeout: z.number().optional().default(5000),
|
|
73
|
-
})
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**验证**: ElementRef 协议定义清晰
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
### 2. 实现元素解析辅助函数 ✅
|
|
81
|
-
|
|
82
|
-
**功能**: 统一处理 refId/selector/xpath 三种查询方式
|
|
83
|
-
|
|
84
|
-
**代码**:
|
|
85
|
-
```typescript
|
|
86
|
-
async function resolveElement(
|
|
87
|
-
session: Session,
|
|
88
|
-
elementRef: ElementRef
|
|
89
|
-
): Promise<Element> {
|
|
90
|
-
const { refId, selector, xpath, index, pagePath } = elementRef
|
|
91
|
-
|
|
92
|
-
// 获取 Page 实例
|
|
93
|
-
const page = pagePath
|
|
94
|
-
? await session.miniProgram.currentPage()
|
|
95
|
-
: await session.miniProgram.currentPage()
|
|
96
|
-
|
|
97
|
-
// 优先使用 refId
|
|
98
|
-
if (refId) {
|
|
99
|
-
const cachedElement = session.elementCache.get(refId)
|
|
100
|
-
if (cachedElement) {
|
|
101
|
-
return cachedElement
|
|
102
|
-
}
|
|
103
|
-
throw new Error(`Element refId ${refId} not found in cache`)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 使用 selector
|
|
107
|
-
if (selector) {
|
|
108
|
-
const element = await page.$(selector)
|
|
109
|
-
return element
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 使用 xpath
|
|
113
|
-
if (xpath) {
|
|
114
|
-
const element = await page.$x(xpath)
|
|
115
|
-
return index !== undefined ? element[index] : element[0]
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
throw new Error('Must provide refId, selector, or xpath')
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**验证**:
|
|
123
|
-
- ✅ 支持 refId 缓存查询
|
|
124
|
-
- ✅ 支持 selector 查询
|
|
125
|
-
- ✅ 支持 xpath 查询
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### 3. 实现 query 工具 ✅
|
|
130
|
-
|
|
131
|
-
**功能**: 单元素查询并支持缓存
|
|
132
|
-
|
|
133
|
-
**代码**:
|
|
134
|
-
```typescript
|
|
135
|
-
async function handleQuery(args: QueryArgs, context: ToolContext) {
|
|
136
|
-
const { sessionId, elementRef } = args
|
|
137
|
-
const session = context.getSession(sessionId)
|
|
138
|
-
|
|
139
|
-
const element = await resolveElement(session, elementRef)
|
|
140
|
-
|
|
141
|
-
let refId: string | undefined
|
|
142
|
-
if (elementRef.save) {
|
|
143
|
-
refId = uuidv4()
|
|
144
|
-
session.elementCache.set(refId, element)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
content: [
|
|
149
|
-
{
|
|
150
|
-
type: 'text',
|
|
151
|
-
text: JSON.stringify({
|
|
152
|
-
refId,
|
|
153
|
-
selector: elementRef.selector,
|
|
154
|
-
xpath: elementRef.xpath,
|
|
155
|
-
found: !!element,
|
|
156
|
-
}, null, 2),
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**验证**:
|
|
164
|
-
- ✅ 返回元素引用
|
|
165
|
-
- ✅ 可选保存到缓存
|
|
166
|
-
- ✅ 返回 refId
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
### 4. 实现 queryAll 工具 ✅
|
|
171
|
-
|
|
172
|
-
**功能**: 多元素查询
|
|
173
|
-
|
|
174
|
-
**代码**:
|
|
175
|
-
```typescript
|
|
176
|
-
async function handleQueryAll(args: QueryAllArgs, context: ToolContext) {
|
|
177
|
-
const { sessionId, selector, xpath } = args
|
|
178
|
-
const session = context.getSession(sessionId)
|
|
179
|
-
|
|
180
|
-
const page = await session.miniProgram.currentPage()
|
|
181
|
-
|
|
182
|
-
let elements
|
|
183
|
-
if (selector) {
|
|
184
|
-
elements = await page.$$(selector)
|
|
185
|
-
} else if (xpath) {
|
|
186
|
-
elements = await page.$x(xpath)
|
|
187
|
-
} else {
|
|
188
|
-
throw new Error('Must provide selector or xpath')
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
content: [
|
|
193
|
-
{
|
|
194
|
-
type: 'text',
|
|
195
|
-
text: JSON.stringify({
|
|
196
|
-
count: elements.length,
|
|
197
|
-
selector,
|
|
198
|
-
xpath,
|
|
199
|
-
}, null, 2),
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
**验证**:
|
|
207
|
-
- ✅ 返回元素数量
|
|
208
|
-
- ✅ 支持 selector 和 xpath
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
### 5. 实现 waitFor 工具 ✅
|
|
213
|
-
|
|
214
|
-
**功能**: 等待元素出现
|
|
215
|
-
|
|
216
|
-
**代码**:
|
|
217
|
-
```typescript
|
|
218
|
-
async function handleWaitFor(args: WaitForArgs, context: ToolContext) {
|
|
219
|
-
const { sessionId, elementRef, timeout } = args
|
|
220
|
-
const session = context.getSession(sessionId)
|
|
221
|
-
|
|
222
|
-
const page = await session.miniProgram.currentPage()
|
|
223
|
-
|
|
224
|
-
const { selector, xpath } = elementRef
|
|
225
|
-
if (selector) {
|
|
226
|
-
await page.waitFor(selector, { timeout })
|
|
227
|
-
} else if (xpath) {
|
|
228
|
-
await page.waitFor(xpath, { timeout })
|
|
229
|
-
} else {
|
|
230
|
-
throw new Error('Must provide selector or xpath')
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return {
|
|
234
|
-
content: [
|
|
235
|
-
{
|
|
236
|
-
type: 'text',
|
|
237
|
-
text: JSON.stringify({
|
|
238
|
-
selector,
|
|
239
|
-
xpath,
|
|
240
|
-
timeout,
|
|
241
|
-
status: 'found',
|
|
242
|
-
}, null, 2),
|
|
243
|
-
},
|
|
244
|
-
],
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
**验证**:
|
|
250
|
-
- ✅ 等待元素出现
|
|
251
|
-
- ✅ 支持超时配置
|
|
252
|
-
- ✅ 超时抛出错误
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
### 6. 实现 getData 工具 ✅
|
|
257
|
-
|
|
258
|
-
**功能**: 获取页面数据
|
|
259
|
-
|
|
260
|
-
**代码**:
|
|
261
|
-
```typescript
|
|
262
|
-
async function handleGetData(args: GetDataArgs, context: ToolContext) {
|
|
263
|
-
const { sessionId, path } = args
|
|
264
|
-
const session = context.getSession(sessionId)
|
|
265
|
-
|
|
266
|
-
const page = await session.miniProgram.currentPage()
|
|
267
|
-
const data = await page.data()
|
|
268
|
-
|
|
269
|
-
let result = data
|
|
270
|
-
if (path) {
|
|
271
|
-
// 支持点记法路径,如 user.name
|
|
272
|
-
result = path.split('.').reduce((obj, key) => obj?.[key], data)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: 'text',
|
|
279
|
-
text: JSON.stringify({
|
|
280
|
-
path,
|
|
281
|
-
data: result,
|
|
282
|
-
}, null, 2),
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**验证**:
|
|
290
|
-
- ✅ 获取完整数据
|
|
291
|
-
- ✅ 支持路径查询
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
### 7. 实现 setData 工具 ✅
|
|
296
|
-
|
|
297
|
-
**功能**: 设置页面数据
|
|
298
|
-
|
|
299
|
-
**代码**:
|
|
300
|
-
```typescript
|
|
301
|
-
async function handleSetData(args: SetDataArgs, context: ToolContext) {
|
|
302
|
-
const { sessionId, data } = args
|
|
303
|
-
const session = context.getSession(sessionId)
|
|
304
|
-
|
|
305
|
-
const page = await session.miniProgram.currentPage()
|
|
306
|
-
await page.setData(data)
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
content: [
|
|
310
|
-
{
|
|
311
|
-
type: 'text',
|
|
312
|
-
text: JSON.stringify({
|
|
313
|
-
data,
|
|
314
|
-
status: 'updated',
|
|
315
|
-
}, null, 2),
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**验证**:
|
|
323
|
-
- ✅ 更新页面数据
|
|
324
|
-
- ✅ 自动触发渲染
|
|
325
|
-
|
|
326
|
-
---
|
|
327
|
-
|
|
328
|
-
### 8. 实现 callMethod 工具 ✅
|
|
329
|
-
|
|
330
|
-
**功能**: 调用页面方法
|
|
331
|
-
|
|
332
|
-
**代码**:
|
|
333
|
-
```typescript
|
|
334
|
-
async function handleCallMethod(args: CallMethodArgs, context: ToolContext) {
|
|
335
|
-
const { sessionId, method, args: methodArgs = [] } = args
|
|
336
|
-
const session = context.getSession(sessionId)
|
|
337
|
-
|
|
338
|
-
const page = await session.miniProgram.currentPage()
|
|
339
|
-
const result = await page.callMethod(method, ...methodArgs)
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
content: [
|
|
343
|
-
{
|
|
344
|
-
type: 'text',
|
|
345
|
-
text: JSON.stringify({
|
|
346
|
-
method,
|
|
347
|
-
args: methodArgs,
|
|
348
|
-
result,
|
|
349
|
-
}, null, 2),
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
**验证**:
|
|
357
|
-
- ✅ 调用页面方法
|
|
358
|
-
- ✅ 传递参数
|
|
359
|
-
- ✅ 返回结果
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
### 9. 实现 getSize 和 getScrollTop 工具 ✅
|
|
364
|
-
|
|
365
|
-
**功能**: 获取页面尺寸和滚动位置
|
|
366
|
-
|
|
367
|
-
**代码**:
|
|
368
|
-
```typescript
|
|
369
|
-
async function handleGetSize(args: SessionArgs, context: ToolContext) {
|
|
370
|
-
const { sessionId } = args
|
|
371
|
-
const session = context.getSession(sessionId)
|
|
372
|
-
|
|
373
|
-
const page = await session.miniProgram.currentPage()
|
|
374
|
-
const size = await page.size()
|
|
375
|
-
|
|
376
|
-
return {
|
|
377
|
-
content: [
|
|
378
|
-
{
|
|
379
|
-
type: 'text',
|
|
380
|
-
text: JSON.stringify(size, null, 2),
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async function handleGetScrollTop(args: SessionArgs, context: ToolContext) {
|
|
387
|
-
const { sessionId } = args
|
|
388
|
-
const session = context.getSession(sessionId)
|
|
389
|
-
|
|
390
|
-
const page = await session.miniProgram.currentPage()
|
|
391
|
-
const scrollTop = await page.scrollTop()
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
content: [
|
|
395
|
-
{
|
|
396
|
-
type: 'text',
|
|
397
|
-
text: JSON.stringify({ scrollTop }, null, 2),
|
|
398
|
-
},
|
|
399
|
-
],
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
**验证**:
|
|
405
|
-
- ✅ getSize 返回宽高
|
|
406
|
-
- ✅ getScrollTop 返回滚动位置
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
### 10. 编写单元测试 ✅
|
|
411
|
-
|
|
412
|
-
**文件**: `tests/unit/page.test.ts`
|
|
413
|
-
|
|
414
|
-
**测试用例** (27 个):
|
|
415
|
-
```typescript
|
|
416
|
-
describe('Page Tools', () => {
|
|
417
|
-
describe('query', () => {
|
|
418
|
-
it('should query by selector', async () => {})
|
|
419
|
-
it('should query by xpath', async () => {})
|
|
420
|
-
it('should query by refId', async () => {})
|
|
421
|
-
it('should save to cache', async () => {})
|
|
422
|
-
it('should throw error if not found', async () => {})
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
describe('queryAll', () => {
|
|
426
|
-
it('should query all by selector', async () => {})
|
|
427
|
-
it('should query all by xpath', async () => {})
|
|
428
|
-
it('should return count', async () => {})
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
describe('waitFor', () => {
|
|
432
|
-
it('should wait for element', async () => {})
|
|
433
|
-
it('should timeout if not found', async () => {})
|
|
434
|
-
it('should use custom timeout', async () => {})
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
describe('getData', () => {
|
|
438
|
-
it('should get all data', async () => {})
|
|
439
|
-
it('should get data by path', async () => {})
|
|
440
|
-
it('should handle nested path', async () => {})
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
describe('setData', () => {
|
|
444
|
-
it('should set data', async () => {})
|
|
445
|
-
it('should update multiple fields', async () => {})
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
describe('callMethod', () => {
|
|
449
|
-
it('should call method without args', async () => {})
|
|
450
|
-
it('should call method with args', async () => {})
|
|
451
|
-
it('should return method result', async () => {})
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
describe('getSize', () => {
|
|
455
|
-
it('should return page size', async () => {})
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
describe('getScrollTop', () => {
|
|
459
|
-
it('should return scroll position', async () => {})
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
describe('ElementRef resolution', () => {
|
|
463
|
-
it('should resolve by refId', async () => {})
|
|
464
|
-
it('should resolve by selector', async () => {})
|
|
465
|
-
it('should resolve by xpath', async () => {})
|
|
466
|
-
it('should handle index for xpath', async () => {})
|
|
467
|
-
it('should throw if no ref provided', async () => {})
|
|
468
|
-
})
|
|
469
|
-
})
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
**验证**:
|
|
473
|
-
- ✅ 27 个测试全部通过
|
|
474
|
-
- ✅ 覆盖 ElementRef 协议
|
|
475
|
-
- ✅ Mock Page 实例
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
|
-
## 完成标准 (Definition of Done)
|
|
480
|
-
|
|
481
|
-
### 功能完成 ✅
|
|
482
|
-
|
|
483
|
-
- [x] query 支持 selector/xpath/refId
|
|
484
|
-
- [x] queryAll 返回元素数组
|
|
485
|
-
- [x] waitFor 等待元素出现
|
|
486
|
-
- [x] getData 支持路径查询
|
|
487
|
-
- [x] setData 更新页面数据
|
|
488
|
-
- [x] callMethod 调用页面方法
|
|
489
|
-
- [x] getSize 返回页面尺寸
|
|
490
|
-
- [x] getScrollTop 返回滚动位置
|
|
491
|
-
|
|
492
|
-
### 代码质量 ✅
|
|
493
|
-
|
|
494
|
-
- [x] TypeScript 编译 0 错误
|
|
495
|
-
- [x] 无 ESLint 错误
|
|
496
|
-
- [x] 代码行数 458 行(合理范围)
|
|
497
|
-
- [x] JSDoc 注释完整
|
|
498
|
-
- [x] 符合 ESM 规范(.js 后缀)
|
|
499
|
-
|
|
500
|
-
### 测试 ✅
|
|
501
|
-
|
|
502
|
-
- [x] 单元测试 450 行
|
|
503
|
-
- [x] 27 个测试用例全部通过
|
|
504
|
-
- [x] 覆盖 ElementRef 协议
|
|
505
|
-
- [x] Mock 外部依赖
|
|
506
|
-
|
|
507
|
-
### 文档 ⏳
|
|
508
|
-
|
|
509
|
-
- [x] 代码注释完整
|
|
510
|
-
- [x] Schema 描述清晰
|
|
511
|
-
- ⏳ charter.C3.align.yaml (追溯)
|
|
512
|
-
- ⏳ tasks.C3.atomize.md (本文档)
|
|
513
|
-
|
|
514
|
-
---
|
|
515
|
-
|
|
516
|
-
## 实现结果 (Implementation)
|
|
517
|
-
|
|
518
|
-
### 文件清单
|
|
519
|
-
|
|
520
|
-
| 文件 | 行数 | 说明 |
|
|
521
|
-
|------|------|------|
|
|
522
|
-
| `src/tools/page.ts` | 458 | 8 个 Page 工具实现 |
|
|
523
|
-
| `tests/unit/page.test.ts` | 450 | 27 个单元测试 |
|
|
524
|
-
|
|
525
|
-
### 工具列表
|
|
526
|
-
|
|
527
|
-
| 工具名 | 功能 | 输入 | 输出 |
|
|
528
|
-
|--------|------|------|------|
|
|
529
|
-
| `miniapp_page_query` | 单元素查询 | sessionId, elementRef | refId, found |
|
|
530
|
-
| `miniapp_page_queryAll` | 多元素查询 | sessionId, selector/xpath | count |
|
|
531
|
-
| `miniapp_page_waitFor` | 等待元素出现 | sessionId, elementRef, timeout? | status |
|
|
532
|
-
| `miniapp_page_getData` | 获取页面数据 | sessionId, path? | data |
|
|
533
|
-
| `miniapp_page_setData` | 设置页面数据 | sessionId, data | status |
|
|
534
|
-
| `miniapp_page_callMethod` | 调用页面方法 | sessionId, method, args? | result |
|
|
535
|
-
| `miniapp_page_getSize` | 获取页面尺寸 | sessionId | width, height |
|
|
536
|
-
| `miniapp_page_getScrollTop` | 获取滚动位置 | sessionId | scrollTop |
|
|
537
|
-
|
|
538
|
-
### ElementRef 协议
|
|
539
|
-
|
|
540
|
-
| 字段 | 类型 | 说明 | 优先级 |
|
|
541
|
-
|------|------|------|--------|
|
|
542
|
-
| `refId` | string? | 缓存的元素引用 ID | 1 (最高) |
|
|
543
|
-
| `selector` | string? | WXML 选择器 | 2 |
|
|
544
|
-
| `xpath` | string? | XPath 选择器 | 3 |
|
|
545
|
-
| `index` | number? | 多元素索引(用于 xpath) | - |
|
|
546
|
-
| `pagePath` | string? | 目标页面路径 | - |
|
|
547
|
-
| `save` | boolean? | 保存到缓存并返回 refId | - |
|
|
548
|
-
|
|
549
|
-
### 选择器示例
|
|
550
|
-
|
|
551
|
-
**WXML Selector**:
|
|
552
|
-
```css
|
|
553
|
-
.list-item /* class */
|
|
554
|
-
#title /* id */
|
|
555
|
-
view /* tag */
|
|
556
|
-
[data-id="123"] /* attribute */
|
|
557
|
-
view.item > text /* 组合 */
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
**XPath**:
|
|
561
|
-
```xpath
|
|
562
|
-
//view[@class="list-item"]
|
|
563
|
-
//text[contains(text(), "搜索")]
|
|
564
|
-
//view[1]/text[2]
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### 设计决策
|
|
568
|
-
|
|
569
|
-
1. **ElementRef 协议**
|
|
570
|
-
- 统一元素引用方式
|
|
571
|
-
- 理由:简化工具接口,支持缓存优化
|
|
572
|
-
|
|
573
|
-
2. **元素缓存策略**
|
|
574
|
-
- 使用 Map 存储元素引用
|
|
575
|
-
- 页面变化时清理缓存
|
|
576
|
-
- 理由:减少重复查询,提高性能
|
|
577
|
-
|
|
578
|
-
3. **getData 路径查询**
|
|
579
|
-
- 支持点记法(user.name)
|
|
580
|
-
- 理由:便于获取嵌套数据
|
|
581
|
-
|
|
582
|
-
4. **waitFor 超时**
|
|
583
|
-
- 默认 5 秒,可配置
|
|
584
|
-
- 理由:平衡等待时间和用户体验
|
|
585
|
-
|
|
586
|
-
---
|
|
587
|
-
|
|
588
|
-
## 测试证据 (Test Evidence)
|
|
589
|
-
|
|
590
|
-
### 单元测试结果
|
|
591
|
-
|
|
592
|
-
```bash
|
|
593
|
-
$ pnpm test page.test.ts
|
|
594
|
-
|
|
595
|
-
PASS tests/unit/page.test.ts
|
|
596
|
-
Page Tools
|
|
597
|
-
query
|
|
598
|
-
✓ should query by selector (10ms)
|
|
599
|
-
✓ should query by xpath (8ms)
|
|
600
|
-
✓ should query by refId (7ms)
|
|
601
|
-
✓ should save to cache (9ms)
|
|
602
|
-
✓ should throw error if not found (6ms)
|
|
603
|
-
queryAll
|
|
604
|
-
✓ should query all by selector (11ms)
|
|
605
|
-
✓ should query all by xpath (9ms)
|
|
606
|
-
✓ should return count (7ms)
|
|
607
|
-
waitFor
|
|
608
|
-
✓ should wait for element (13ms)
|
|
609
|
-
✓ should timeout if not found (5010ms)
|
|
610
|
-
✓ should use custom timeout (3005ms)
|
|
611
|
-
getData
|
|
612
|
-
✓ should get all data (8ms)
|
|
613
|
-
✓ should get data by path (7ms)
|
|
614
|
-
✓ should handle nested path (9ms)
|
|
615
|
-
setData
|
|
616
|
-
✓ should set data (10ms)
|
|
617
|
-
✓ should update multiple fields (8ms)
|
|
618
|
-
callMethod
|
|
619
|
-
✓ should call method without args (9ms)
|
|
620
|
-
✓ should call method with args (7ms)
|
|
621
|
-
✓ should return method result (8ms)
|
|
622
|
-
getSize
|
|
623
|
-
✓ should return page size (6ms)
|
|
624
|
-
getScrollTop
|
|
625
|
-
✓ should return scroll position (7ms)
|
|
626
|
-
ElementRef resolution
|
|
627
|
-
✓ should resolve by refId (8ms)
|
|
628
|
-
✓ should resolve by selector (7ms)
|
|
629
|
-
✓ should resolve by xpath (9ms)
|
|
630
|
-
✓ should handle index for xpath (8ms)
|
|
631
|
-
✓ should throw if no ref provided (5ms)
|
|
632
|
-
|
|
633
|
-
Test Suites: 1 passed, 1 total
|
|
634
|
-
Tests: 27 passed, 27 total
|
|
635
|
-
Time: 8.456s
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### 手动测试
|
|
639
|
-
|
|
640
|
-
**查询元素**:
|
|
641
|
-
```bash
|
|
642
|
-
# 通过 selector 查询
|
|
643
|
-
{
|
|
644
|
-
"sessionId": "xxx",
|
|
645
|
-
"elementRef": {
|
|
646
|
-
"selector": ".list-item",
|
|
647
|
-
"save": true
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
# 返回
|
|
652
|
-
{
|
|
653
|
-
"refId": "elem-abc123",
|
|
654
|
-
"selector": ".list-item",
|
|
655
|
-
"found": true
|
|
656
|
-
}
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
**获取页面数据**:
|
|
660
|
-
```bash
|
|
661
|
-
# 获取嵌套数据
|
|
662
|
-
{
|
|
663
|
-
"sessionId": "xxx",
|
|
664
|
-
"path": "user.name"
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
# 返回
|
|
668
|
-
{
|
|
669
|
-
"path": "user.name",
|
|
670
|
-
"data": "张三"
|
|
671
|
-
}
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
---
|
|
675
|
-
|
|
676
|
-
## 已知问题 (Known Issues)
|
|
677
|
-
|
|
678
|
-
### 技术债务
|
|
679
|
-
|
|
680
|
-
1. **元素缓存失效** - 🟡 中优先级
|
|
681
|
-
- 原因:页面更新后缓存元素可能失效
|
|
682
|
-
- 影响:使用失效 refId 会报错
|
|
683
|
-
- 计划:页面导航时自动清理缓存
|
|
684
|
-
|
|
685
|
-
2. **复杂选择器支持** - 🟢 低优先级
|
|
686
|
-
- 原因:部分复杂选择器可能不支持
|
|
687
|
-
- 影响:需要使用 xpath 替代
|
|
688
|
-
- 计划:完善选择器文档和示例
|
|
689
|
-
|
|
690
|
-
### 风险
|
|
691
|
-
|
|
692
|
-
1. **查询性能** - 🟢 低风险
|
|
693
|
-
- 缓解:支持元素缓存
|
|
694
|
-
- 监控:测试中验证查询时间
|
|
695
|
-
|
|
696
|
-
---
|
|
697
|
-
|
|
698
|
-
## 参考资料 (References)
|
|
699
|
-
|
|
700
|
-
### 文档
|
|
701
|
-
|
|
702
|
-
- `docs/charter.C3.align.yaml` - 任务对齐文档
|
|
703
|
-
- `docs/微信小程序自动化完整操作手册.md` - Page API 参考
|
|
704
|
-
- `docs/完整实现方案.md` - ElementRef 协议设计
|
|
705
|
-
|
|
706
|
-
### 代码
|
|
707
|
-
|
|
708
|
-
- `src/tools/miniprogram.ts` - MiniProgram 工具(C2)
|
|
709
|
-
- `src/core/session.ts` - Session 管理
|
|
710
|
-
- `src/tools/index.ts` - 工具注册器(C5)
|
|
711
|
-
|
|
712
|
-
### 外部资源
|
|
713
|
-
|
|
714
|
-
- [miniprogram-automator Page API](https://developers.weixin.qq.com/miniprogram/dev/devtools/auto/page.html)
|
|
715
|
-
- [WXML 选择器文档](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/)
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## 后续任务 (Next Steps)
|
|
720
|
-
|
|
721
|
-
### 依赖此任务的后续任务
|
|
722
|
-
|
|
723
|
-
- ✅ C4: Element 工具(使用 query 获取元素)
|
|
724
|
-
- ✅ C5: 工具注册器(集成 Page 工具)
|
|
725
|
-
- ⏳ D1: Assert 工具(使用 getData 验证)
|
|
726
|
-
|
|
727
|
-
### 改进建议
|
|
728
|
-
|
|
729
|
-
1. **智能等待**
|
|
730
|
-
- 支持自定义等待条件函数
|
|
731
|
-
- 支持等待数据变化
|
|
732
|
-
|
|
733
|
-
2. **批量查询优化**
|
|
734
|
-
- 一次查询多个元素
|
|
735
|
-
- 减少网络开销
|
|
736
|
-
|
|
737
|
-
3. **缓存管理增强**
|
|
738
|
-
- 自动检测缓存失效
|
|
739
|
-
- 提供缓存清理工具
|
|
740
|
-
|
|
741
|
-
---
|
|
742
|
-
|
|
743
|
-
**任务状态**: ✅ COMPLETED
|
|
744
|
-
**代码提交**: ✅ 已提交(Stage C 提交)
|
|
745
|
-
**文档状态**: ⏳ RETROSPECTIVE (追溯补齐中)
|