@reglow/review-mcp 0.1.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 +238 -0
- package/catalog.mjs +537 -0
- package/cli-utils.mjs +29 -0
- package/index.mjs +192 -0
- package/integrations.mjs +270 -0
- package/knowledge.mjs +1130 -0
- package/package.json +48 -0
- package/server.mjs +742 -0
- package/setup.mjs +404 -0
- package/site.mjs +255 -0
- package/skills/review-components/BEST_PRACTICES.md +132 -0
- package/skills/review-components/COMPONENTS.md +216 -0
- package/skills/review-components/SKILL.md +138 -0
- package/skills/review-components/WORKFLOWS.md +435 -0
- package/skills.mjs +194 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# ReView MCP 工作流(WORKFLOWS)
|
|
2
|
+
|
|
3
|
+
> 本文件说明如何用 review-mcp 的 7 个工具**确定性地**拿到 ReView 组件的真实文档与源码,并给出 6 个可直接照做的完整任务示例。
|
|
4
|
+
> 事实来源:`review/README.md`、`review/docs/components/*.md`、`review/docs/guide/*.md`、`review/docs/.vitepress/sidebar.ts`、`review/uni_modules/reglow-review/components/re-*/`、`review/CHANGELOG.md`。
|
|
5
|
+
|
|
6
|
+
## 1. MCP 工具概览
|
|
7
|
+
|
|
8
|
+
| 工具 | 用途 | 关键参数 | 典型返回 |
|
|
9
|
+
| -------------------------- | -------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
10
|
+
| `get_review_document` | 取组件完整文档 | `component`(可选,不传返回按分类的组件总览)、`category`、`version` | 文档正文、API 表格(Props / Events / Slots)、示例说明 |
|
|
11
|
+
| `get_review_code_examples` | 提取可直接复制的 uvue/uts 示例 | `component`、序号、类型过滤、`version` | 代码块 + 所在示例标题 |
|
|
12
|
+
| `list_component_files` | 列组件源码文件清单 | `component`、`version` | 路径 / 行数 / 大小(如 `re-button/re-button.uvue`、`readme.md`) |
|
|
13
|
+
| `get_file_code` | 读具体文件内容 | `component`、文件路径、行号区间、`version` | 源码片段(可定位 `Props` 类型与 `withDefaults`) |
|
|
14
|
+
| `get_function_code` | 取某个函数 / 常量 / 组件定义的完整实现 | `component`、符号名、`version` | 实现代码;找不到时返回可用符号清单 |
|
|
15
|
+
| `search_review_docs` | 跨文档与源码关键词检索 | 关键词(可多个)、`category`、`limit`、`version` | `组件/文件:行号` + 上下文片段 |
|
|
16
|
+
| `get_review_guide` | 库级指南 | `topic`:安装 / easycom / 主题 / i18n / 平台 / 约定 等 | 库级说明 |
|
|
17
|
+
|
|
18
|
+
**共同参数 `version`**:不传 = 项目本地已安装版本(当前基线 **1.0.6**,见 `CHANGELOG.md`)。
|
|
19
|
+
|
|
20
|
+
## 2. 基础查询流程(四步走)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
① 定位 get_review_document(可带 category) → 确认组件存在、拿到组件名与一句话说明
|
|
24
|
+
② 详情 get_review_document { component } → 文档正文 + Props/Events/Slots 表格
|
|
25
|
+
③ 示例 get_review_code_examples { component } → 可复制代码
|
|
26
|
+
④ 下钻 list_component_files → get_file_code → 读 .uvue 的 Props 类型 / withDefaults / defineEmits
|
|
27
|
+
get_function_code
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
每一步的**判断标准**:
|
|
31
|
+
|
|
32
|
+
| 步骤 | 结果够用的标准 | 不够时的动作 |
|
|
33
|
+
| ------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
34
|
+
| ① 定位 | 目标组件出现在总览中,与 `docs/components/<name>.md` 对得上 | 换关键词走 `search_review_docs`;仍无 → 判定「该组件不存在」,不要造名字 |
|
|
35
|
+
| ② 详情 | 找到目标 prop/event 的**表格行** | 表格是 `-`(ReView 文档常见)→ 走 ④ 读源码,而不是猜测 |
|
|
36
|
+
| ③ 示例 | 有与本需求同场景的示例 | 取最接近的示例 + 用 ②/④ 的 API 自行拼装,只使用已确认存在的 prop |
|
|
37
|
+
| ④ 下钻 | 在 `re-*/*.uvue` 的 `type Props` 或 `withDefaults(defineProps<Props>(), ...)` 中找到字段 | 用 `get_function_code` 取整段实现;仍无 → 说明该能力未实现 |
|
|
38
|
+
|
|
39
|
+
**查询用语建议**:组件名用文档文件名(`button` / `infinite-loading` / `date-time-picker`),标签名 = `re-` + 文件名(`<re-infinite-loading>`);中文名(「级联选择器」「搜索历史」)留给 `search_review_docs`。
|
|
40
|
+
|
|
41
|
+
### 2.1 实测:文档表格为空的组件(必须下钻源码)
|
|
42
|
+
|
|
43
|
+
| 组件 | 文档情况 | 源码位置 |
|
|
44
|
+
| ------------------------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------- |
|
|
45
|
+
| `input` | Props 表为 `-`(Events / Slots 完整) | `re-input/re-input.uvue` |
|
|
46
|
+
| `cascader` | Props 表为 `-` | `re-cascader/re-cascader.uvue` |
|
|
47
|
+
| `form` / `form-item` | Props 表为 `-` | `re-form/re-form.uvue`、`re-form-item/re-form-item.uvue` |
|
|
48
|
+
| `list`、`infinite-loading`、`button`、`address`、`search-history`、`config-provider` | Props 表完整,可直接照抄 | 各自 `readme.md` / `.uvue` |
|
|
49
|
+
|
|
50
|
+
> 口径提示:`docs/components/*.md` 是文档站产物,个别版本存在「文档落后于源码」的情况。典型例:`re-address` 文档只列了 `visible` / `value` / `provinces` / `customStyle`,而源码(1.0.5)已有 `lazy` / `loading` prop 与 `level-select` 事件——以源码为最终依据,并在回答中说明来源。
|
|
51
|
+
|
|
52
|
+
## 3. 完整任务示例
|
|
53
|
+
|
|
54
|
+
### 示例 1:给页面新增按钮与输入框(`re-button` / `re-input`)
|
|
55
|
+
|
|
56
|
+
| 步骤 | 调用 | 参数 | 判断 |
|
|
57
|
+
| ---- | --------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| 1 | `get_review_document` | `{ component: "button" }` | 确认 `Button Props` 全表:`theme`(default/primary/danger/warning/success)、`size`(small/medium/large)、`shape`(rectangle/round/square/circle)、`variant`(base/outline/text/dashed)、`block`、`disabled`、`loading`、`icon`、`content`、`customStyle`、`customClass`;事件只有 `click`;插槽 `default` |
|
|
59
|
+
| 2 | `get_review_document` | `{ component: "input" }` | Props 表为 `-` → 需要确认输入框 props 时走第 3 步;Events 明确有 `update:value`(value: string)、`input`(value: string)、`change`/`focus`/`blur`/`clear`/`click`(detail: UTSJSONObject) |
|
|
60
|
+
| 3 | `get_file_code` | `{ component: "input", file: "re-input.uvue", startLine: 1, endLine: 120 }` | 只取真实存在的 props;文档已给的 `v-model:value` + `placeholder` 用法(见 README 示例)可直接用 |
|
|
61
|
+
| 4 | `get_review_guide` | `{ topic: "easycom" }` | 确认 easycom 只需 `autoscan: true`,标签即 `<re-button>`,无需 import |
|
|
62
|
+
|
|
63
|
+
产出(Props / Events 取自 `docs/components/button.md`、`docs/components/input.md`,用法取自 `README.md` 的 easycom 示例):
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<template>
|
|
67
|
+
<re-config-provider locale="zh-CN">
|
|
68
|
+
<view class="page">
|
|
69
|
+
<!-- 主按钮:主题 / 尺寸 / 块级 / 加载态 -->
|
|
70
|
+
<re-button
|
|
71
|
+
theme="primary"
|
|
72
|
+
size="large"
|
|
73
|
+
block
|
|
74
|
+
:loading="submitting"
|
|
75
|
+
@click="handleSubmit"
|
|
76
|
+
>
|
|
77
|
+
提交
|
|
78
|
+
</re-button>
|
|
79
|
+
|
|
80
|
+
<!-- 次要按钮:变体 + 形状 -->
|
|
81
|
+
<re-button
|
|
82
|
+
theme="default"
|
|
83
|
+
variant="outline"
|
|
84
|
+
shape="round"
|
|
85
|
+
@click="handleCancel"
|
|
86
|
+
>
|
|
87
|
+
取消
|
|
88
|
+
</re-button>
|
|
89
|
+
|
|
90
|
+
<!-- 输入框:v-model:value + 事件 -->
|
|
91
|
+
<re-input
|
|
92
|
+
v-model:value="keyword"
|
|
93
|
+
placeholder="请输入手机号"
|
|
94
|
+
@input="handleInput"
|
|
95
|
+
@clear="handleClear"
|
|
96
|
+
/>
|
|
97
|
+
</view>
|
|
98
|
+
</re-config-provider>
|
|
99
|
+
</template>
|
|
100
|
+
|
|
101
|
+
<script setup lang="uts">
|
|
102
|
+
import { ref } from 'vue'
|
|
103
|
+
|
|
104
|
+
const submitting = ref(false)
|
|
105
|
+
const keyword = ref('')
|
|
106
|
+
|
|
107
|
+
function handleSubmit() {
|
|
108
|
+
submitting.value = true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handleCancel() {
|
|
112
|
+
keyword.value = ''
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// input 事件参数为 value: string
|
|
116
|
+
function handleInput(value: string) {
|
|
117
|
+
keyword.value = value
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// clear 事件参数为 detail: UTSJSONObject
|
|
121
|
+
function handleClear(detail: UTSJSONObject) {
|
|
122
|
+
keyword.value = ''
|
|
123
|
+
}
|
|
124
|
+
</script>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> 注意:`re-button` 的文案既可用默认插槽(`<re-button>提交</re-button>`),也可用 `content` prop;`icon` 支持字体图标名或图片地址。事件只有一个 `click`,不要写 `@tap`、`@confirm`。
|
|
128
|
+
|
|
129
|
+
### 示例 2:上滑加载更多 / 长列表(`re-infinite-loading` / `re-list`)
|
|
130
|
+
|
|
131
|
+
| 步骤 | 调用 | 参数 | 判断 |
|
|
132
|
+
| ---- | --------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
+
| 1 | `get_review_document` | `{ component: "infinite-loading" }` | 关键事实:组件本身就是一个 `scroll-view`,用 `lower-threshold` 触发 `load-more`;Props:`hasMore`(boolean, true)、`loadingText`、`loadMoreText`、`noMoreText`、`threshold`(number, 50)、`customStyle`;Events:`load-more`(**无参**)、`update:hasMore`(value: boolean);Slots:`default`、`loading`、`no-more` |
|
|
134
|
+
| 2 | `get_review_document` | `{ component: "list" }` | Props:`data`(Array\<UTSJSONObject\>)、`itemHeight`(number, 50)、`height`(string, "100%")、`bufferCount`(number, 5)、`keyField`(string, "id")、`customStyle`;Events:`scroll`(scrollTop: number)、`click`(item, index)、`update:data` |
|
|
135
|
+
| 3 | `get_file_code` | `{ component: "list", file: "re-list.uvue", startLine: 1, endLine: 20 }` | 确认默认插槽是**作用域插槽**,暴露 `item` 与 `index`(`<slot :item="item" :index="startIndex + index">`) |
|
|
136
|
+
| 4 | `search_review_docs` | `{ keywords: ["suggestMaxHeight", "虚拟渲染"], limit: 10 }` | 需要「虚拟渲染」在别的组件里的同类写法时用;`re-search-history` 的 `suggestMaxHeight` 即虚拟渲染窗口高度 |
|
|
137
|
+
|
|
138
|
+
产出:
|
|
139
|
+
|
|
140
|
+
```vue
|
|
141
|
+
<!-- 方案 A:触底加载更多。re-infinite-loading 自带 scroll-view,直接包住列表内容 -->
|
|
142
|
+
<template>
|
|
143
|
+
<re-infinite-loading
|
|
144
|
+
:has-more="hasMore"
|
|
145
|
+
loading-text="加载中..."
|
|
146
|
+
load-more-text="加载更多"
|
|
147
|
+
no-more-text="没有更多了"
|
|
148
|
+
:threshold="50"
|
|
149
|
+
@load-more="loadMore"
|
|
150
|
+
@update:hasMore="(v) => (hasMore = v)"
|
|
151
|
+
>
|
|
152
|
+
<view v-for="(item, index) in list" :key="index" class="item">
|
|
153
|
+
<text>{{ item.title }}</text>
|
|
154
|
+
</view>
|
|
155
|
+
</re-infinite-loading>
|
|
156
|
+
</template>
|
|
157
|
+
|
|
158
|
+
<script setup lang="uts">
|
|
159
|
+
import { ref } from 'vue'
|
|
160
|
+
|
|
161
|
+
const list = ref([] as Array<UTSJSONObject>)
|
|
162
|
+
const hasMore = ref(true)
|
|
163
|
+
|
|
164
|
+
function loadMore() {
|
|
165
|
+
// load-more 事件没有参数,自己维护分页页码
|
|
166
|
+
// 拉到数据后 append 到 list,并在没有下一页时 hasMore.value = false
|
|
167
|
+
}
|
|
168
|
+
</script>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```vue
|
|
172
|
+
<!-- 方案 B:数据量大(成百上千行)用 re-list 虚拟列表,固定行高 + 渲染窗口 -->
|
|
173
|
+
<template>
|
|
174
|
+
<re-list
|
|
175
|
+
:data="list"
|
|
176
|
+
:item-height="56"
|
|
177
|
+
height="100%"
|
|
178
|
+
key-field="id"
|
|
179
|
+
:buffer-count="5"
|
|
180
|
+
@click="onItemClick"
|
|
181
|
+
>
|
|
182
|
+
<template #default="{ item, index }">
|
|
183
|
+
<view class="row">
|
|
184
|
+
<text>{{ index }} · {{ item.title }}</text>
|
|
185
|
+
</view>
|
|
186
|
+
</template>
|
|
187
|
+
</re-list>
|
|
188
|
+
</template>
|
|
189
|
+
|
|
190
|
+
<script setup lang="uts">
|
|
191
|
+
import { ref } from 'vue'
|
|
192
|
+
|
|
193
|
+
const list = ref([] as Array<UTSJSONObject>)
|
|
194
|
+
|
|
195
|
+
function onItemClick(item: UTSJSONObject, index: number) {
|
|
196
|
+
// click 事件参数为 item: UTSJSONObject, index: number
|
|
197
|
+
}
|
|
198
|
+
</script>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> **不要嵌套**:`re-infinite-loading` 与 `re-list` 各自都是滚动容器(内部都是 `scroll-view`),把两者互相嵌套会出现双层滚动,按场景二选一。`re-list` 依赖固定 `itemHeight`,行高不固定的列表不适合它。
|
|
202
|
+
|
|
203
|
+
### 示例 3:省市区 / 多级联动选择(`re-address` / `re-cascader`)
|
|
204
|
+
|
|
205
|
+
| 步骤 | 调用 | 参数 | 判断 |
|
|
206
|
+
| ---- | --------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
207
|
+
| 1 | `get_review_document` | `{ component: "address" }` | 文档口径:Props `visible` / `value`(Array\<string\>,[省,市,区]) / `provinces` / `customStyle`;Events `confirm`(value)、`cancel`、`close`、`update:visible` |
|
|
208
|
+
| 2 | `get_file_code` | `{ component: "address", file: "re-address.uvue" }` | **源码比文档多**:`lazy`(boolean, false)、`loading`(boolean, false) 两个 prop 与 `level-select`(path: Array\<string\>) 事件(1.0.5 新增懒加载模式) |
|
|
209
|
+
| 3 | `get_review_document` | `{ component: "cascader" }` | 文档 Props 表为 `-`,Events 有 `change`(Array\<string\>) / `close` / `update:visible` |
|
|
210
|
+
| 4 | `get_file_code` | `{ component: "cascader", file: "re-cascader.uvue", startLine: 86, endLine: 110 }` | 源码 Props:`value`、`options`(Array\<{value,label,children?}\>)、`visible`、`title`、`placeholder`、`customStyle`、`lazy` |
|
|
211
|
+
|
|
212
|
+
产出(固定数据用 `re-address`):
|
|
213
|
+
|
|
214
|
+
```vue
|
|
215
|
+
<template>
|
|
216
|
+
<re-address
|
|
217
|
+
:visible="showAddress"
|
|
218
|
+
:value="selected"
|
|
219
|
+
:provinces="provinces"
|
|
220
|
+
@update:visible="(v) => (showAddress = v)"
|
|
221
|
+
@confirm="onConfirm"
|
|
222
|
+
@cancel="showAddress = false"
|
|
223
|
+
/>
|
|
224
|
+
</template>
|
|
225
|
+
|
|
226
|
+
<script setup lang="uts">
|
|
227
|
+
import { ref } from 'vue'
|
|
228
|
+
|
|
229
|
+
const showAddress = ref(false)
|
|
230
|
+
const selected = ref([] as Array<string>)
|
|
231
|
+
// provinces 为省市区数据,结构为 { name, children }(getItemName 取 item['name'])
|
|
232
|
+
const provinces = ref([] as Array<any>)
|
|
233
|
+
|
|
234
|
+
function onConfirm(value: Array<string>) {
|
|
235
|
+
selected.value = value // [省, 市, 区]
|
|
236
|
+
showAddress.value = false
|
|
237
|
+
}
|
|
238
|
+
</script>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
产出(数据量大、按级拉取用 `lazy`):
|
|
242
|
+
|
|
243
|
+
```vue
|
|
244
|
+
<template>
|
|
245
|
+
<!-- re-cascader 的懒加载:选中末级不自动关闭,等外部更新 options 后自动展开下一列 -->
|
|
246
|
+
<re-cascader
|
|
247
|
+
:visible="showCascader"
|
|
248
|
+
:value="selected"
|
|
249
|
+
:options="options"
|
|
250
|
+
title="选择地区"
|
|
251
|
+
placeholder="请选择"
|
|
252
|
+
lazy
|
|
253
|
+
@change="(value) => (selected = value)"
|
|
254
|
+
@update:visible="(v) => (showCascader = v)"
|
|
255
|
+
/>
|
|
256
|
+
</template>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
> `re-address` 的懒加载(`lazy` + `level-select` + `loading`)写法同上:选中某一级且下一级为空时抛出 `level-select`,外部把下级数据合并进 `provinces` 后列表自动刷新,无需关闭面板;加载期间用 `loading` 展示「加载中…」(无数据时文案为「加载中…/暂无可选项」,见 `CHANGELOG.md` 1.0.5)。
|
|
260
|
+
|
|
261
|
+
### 示例 4:表单与校验(`re-form` / `re-form-item`)
|
|
262
|
+
|
|
263
|
+
| 步骤 | 调用 | 参数 | 判断 |
|
|
264
|
+
| ---- | --------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
265
|
+
| 1 | `get_review_document` | `{ component: "form" }` | 文档 Props 表为 `-`;Events `reset` / `submit` / `validate`(detail: UTSJSONObject);`FormItem Slots`:`label` / `default` / `help` |
|
|
266
|
+
| 2 | `get_file_code` | `{ component: "form", file: "re-form.uvue", startLine: 55, endLine: 96 }` | 源码 `type Props`:`data` / `model` / `rules` / `errorMessage` / `labelAlign`(right) / `labelWidth`(81px) / `contentAlign`(left) / `requiredMark`(true) / `requiredMarkPosition`(right) / `resetType`('empty') / `scrollToFirstError`('') / `showErrorMessage`(true) / `submitWithWarningMessage`(false) / `colon`(false) / `customStyle` |
|
|
267
|
+
| 3 | `get_file_code` | `{ component: "form", file: "re-form.uvue", startLine: 458, endLine: 467 }` | `defineExpose` 暴露 `validate` / `validateOnly` / `submit` / `getValidate` / `reset` / `clearValidate` / `setValidateMessage` |
|
|
268
|
+
| 4 | `get_file_code` | `{ component: "form-item", file: "re-form-item.uvue", startLine: 87, endLine: 113 }` | FormItem Props:`label`、`name`、`required`、`rules`(Array\<UTSJSONObject\>)、`help`、`arrow`、`labelAlign`、`labelWidth`、`contentAlign`、`requiredMark`、`showErrorMessage`、`customStyle` |
|
|
269
|
+
| 5 | `get_file_code` | `{ component: "form-item", file: "re-form-item.uvue", startLine: 479, endLine: 605 }` | 规则字段真实可用集合:`required` / `message` / `validator`(value, context) / `whitespace` / `email` / `url` / `date` / `boolean` / `number` / `idcard` / `telnumber` / `pattern` / `enum` / `len` / `min` / `max` / `trigger`(缺省 trigger 视为 `change`) |
|
|
270
|
+
|
|
271
|
+
产出:
|
|
272
|
+
|
|
273
|
+
```vue
|
|
274
|
+
<template>
|
|
275
|
+
<re-form
|
|
276
|
+
:data="formData"
|
|
277
|
+
:rules="rules"
|
|
278
|
+
label-width="90px"
|
|
279
|
+
scroll-to-first-error="re-form-item"
|
|
280
|
+
@submit="onSubmit"
|
|
281
|
+
>
|
|
282
|
+
<re-form-item label="姓名" name="name">
|
|
283
|
+
<re-input v-model:value="formData.name" placeholder="请输入姓名" />
|
|
284
|
+
</re-form-item>
|
|
285
|
+
|
|
286
|
+
<re-form-item label="手机号" name="phone">
|
|
287
|
+
<re-input v-model:value="formData.phone" placeholder="请输入手机号" />
|
|
288
|
+
</re-form-item>
|
|
289
|
+
|
|
290
|
+
<re-form-item label="备注" name="remark" help="最多 50 字">
|
|
291
|
+
<re-textarea v-model:value="formData.remark" />
|
|
292
|
+
</re-form-item>
|
|
293
|
+
|
|
294
|
+
<re-button theme="primary" block @click="submitForm">提交</re-button>
|
|
295
|
+
</re-form>
|
|
296
|
+
</template>
|
|
297
|
+
|
|
298
|
+
<script setup lang="uts">
|
|
299
|
+
import { ref, reactive, getCurrentInstance } from 'vue'
|
|
300
|
+
|
|
301
|
+
const formData = reactive({
|
|
302
|
+
name: '',
|
|
303
|
+
phone: '',
|
|
304
|
+
remark: ''
|
|
305
|
+
} as UTSJSONObject)
|
|
306
|
+
|
|
307
|
+
const rules = {
|
|
308
|
+
name: [{ required: true, message: '请输入姓名' }],
|
|
309
|
+
phone: [
|
|
310
|
+
{ required: true, message: '请输入手机号' },
|
|
311
|
+
{ telnumber: true, message: '手机号格式不正确' }
|
|
312
|
+
],
|
|
313
|
+
remark: [{ max: 50, message: '最多 50 字' }]
|
|
314
|
+
} as UTSJSONObject
|
|
315
|
+
|
|
316
|
+
const formRef = ref<ComponentPublicInstance | null>(null)
|
|
317
|
+
|
|
318
|
+
function submitForm() {
|
|
319
|
+
const inst = formRef.value as any
|
|
320
|
+
if (inst != null) {
|
|
321
|
+
inst.submit()
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function onSubmit(detail: UTSJSONObject) {
|
|
326
|
+
// detail.validateResult === true 表示校验通过;detail.firstError 为首个错误文案
|
|
327
|
+
}
|
|
328
|
+
</script>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
> **规则字段全部来自 `re-form-item.uvue` 源码**(第 479–605 行),不是推测;`re-form` 的 `data` / `model` 二选一,内部 `currentFormData` 优先取 `data`(`re-form.uvue` 第 101–109 行)。
|
|
332
|
+
|
|
333
|
+
### 示例 5:主题与样式定制(`re-config-provider` / `re-theme`)
|
|
334
|
+
|
|
335
|
+
| 步骤 | 调用 | 参数 | 判断 |
|
|
336
|
+
| ---- | --------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
337
|
+
| 1 | `get_review_guide` | `{ topic: "theme" }` 或读 `docs/guide/theme.md` | 令牌前缀 `--re-*`;`re-config-provider` 用 `themeVars` 覆盖变量、`theme="dark"` 切暗色;局部覆盖用 `re-theme` |
|
|
338
|
+
| 2 | `get_review_document` | `{ component: "config-provider" }` | Props:`theme`、`themeVars`(UTSJSONObject)、`locale`(zh-CN)、`localeConfig`、`zIndex`(1000)、`customStyle` |
|
|
339
|
+
| 3 | `get_review_document` | `{ component: "divider" }` | 需要确认目标组件自己有没有 `customStyle` / 颜色类 prop(多数组件都有 `customStyle`) |
|
|
340
|
+
| 4 | `search_review_docs` | `{ keywords: ["--td-", "customStyle"], limit: 20 }` | 核对「这个组件到底读哪个变量」:`re-form` / `re-avatar` / `re-input` / `re-radio` / `re-switch` / `re-form-item` 内部仍用 `--td-*` 变量 |
|
|
341
|
+
|
|
342
|
+
产出:
|
|
343
|
+
|
|
344
|
+
```vue
|
|
345
|
+
<!-- 全局:语言 + 主题变量一起配 -->
|
|
346
|
+
<template>
|
|
347
|
+
<re-config-provider
|
|
348
|
+
locale="zh-CN"
|
|
349
|
+
theme="light"
|
|
350
|
+
:theme-vars="customTheme"
|
|
351
|
+
:z-index="2000"
|
|
352
|
+
>
|
|
353
|
+
<view class="page">
|
|
354
|
+
<re-button theme="primary">主色按钮</re-button>
|
|
355
|
+
</view>
|
|
356
|
+
</re-config-provider>
|
|
357
|
+
</template>
|
|
358
|
+
|
|
359
|
+
<script setup lang="uts">
|
|
360
|
+
const customTheme = {
|
|
361
|
+
'color-primary': '#fa2c19',
|
|
362
|
+
'color-success': '#07c160',
|
|
363
|
+
'color-warning': '#ff976a',
|
|
364
|
+
'color-danger': '#ee0a24',
|
|
365
|
+
'radius-large': '8px',
|
|
366
|
+
'font-size-base': '16px'
|
|
367
|
+
} as UTSJSONObject
|
|
368
|
+
</script>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```vue
|
|
372
|
+
<!-- 局部:只有这个卡片/分区用暗色或其它品牌色 -->
|
|
373
|
+
<template>
|
|
374
|
+
<re-theme theme="dark" :theme-vars="themeVars">
|
|
375
|
+
<view class="section">局部暗色主题</view>
|
|
376
|
+
</re-theme>
|
|
377
|
+
</template>
|
|
378
|
+
|
|
379
|
+
<script setup lang="uts">
|
|
380
|
+
const themeVars = {
|
|
381
|
+
'color-primary': '#0f766e',
|
|
382
|
+
'bg-color-container': '#123b39'
|
|
383
|
+
} as UTSJSONObject
|
|
384
|
+
</script>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- 文档口径的默认令牌值(`docs/guide/theme.md`):`--re-color-primary: #0052d9`、`--re-color-success: #00a870`、`--re-color-warning: #e37318`、`--re-color-danger: #d54941`;圆角 `--re-radius-small/default/medium/large/round` 为 `2px/4px/6px/8px/999px`。
|
|
388
|
+
- 需要把业务值拼成样式字符串时,可复用 `re-common` 的工具函数(`docs/guide/theme.md`):
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import {
|
|
392
|
+
addUnit,
|
|
393
|
+
clamp,
|
|
394
|
+
styles,
|
|
395
|
+
} from "@/uni_modules/reglow-review/utssdk/re-common/interface.uts";
|
|
396
|
+
|
|
397
|
+
const width = addUnit(clamp(360, 280, 420));
|
|
398
|
+
const cardStyle = styles({
|
|
399
|
+
width,
|
|
400
|
+
borderRadius: "12px",
|
|
401
|
+
backgroundColor: "var(--re-bg-color-container)",
|
|
402
|
+
} as UTSJSONObject);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
> `uni.scss` 里的 `$uni-color-*` / `$uni-spacing-*` / `$uni-border-radius-*` 是 DCloud 项目的全局 SCSS 变量(`review/uni.scss`),**不是**组件库主题令牌;改组件外观请用 `themeVars` / `customStyle`,改业务页面自己的样式再用 SCSS 变量。
|
|
406
|
+
|
|
407
|
+
### 示例 6:排障——HBuilderX 编译告警与平台差异
|
|
408
|
+
|
|
409
|
+
| 步骤 | 调用 | 参数 | 判断 |
|
|
410
|
+
| ---- | ---------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------- |
|
|
411
|
+
| 1 | `get_review_guide` | `{ topic: "changelog" }` 或直接读 `CHANGELOG.md` | 先确认当前版本 1.0.6,再看最近两版是否已修过同类问题 |
|
|
412
|
+
| 2 | `search_review_docs` | `{ keywords: ["number | string | symbol", "v-for"], limit: 20 }` | 定位「模板 v-for 索引类型」告警的历史修复范围 |
|
|
413
|
+
| 3 | `get_file_code` | `{ component: "cascader", file: "re-cascader.uvue", startLine: 27, endLine: 80 }` | 确认列渲染是模板内直接 `v-for`(1.0.5 重构点),不再是内部 `re-cascader-item` |
|
|
414
|
+
| 4 | `list_component_files` | `{ component: "indexes" }` | 看到 `page-scroll.uts` + `re-indexes.uvue`,对应 1.0.4 的吸顶重构 |
|
|
415
|
+
|
|
416
|
+
已核实的高频问题与处置(全部来自 `CHANGELOG.md`):
|
|
417
|
+
|
|
418
|
+
| 症状 | 根因 | 处置 |
|
|
419
|
+
| -------------------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
420
|
+
| HBuilderX 编译告警:模板 `v-for` 索引被推断为 `number \| string \| symbol` | UTS 类型推断 | 1.0.4 已批量修复(覆盖 action-sheet、cart-bar、chat-sender、ecard、fixed-nav、footer、product-feed、quick-enter、segmented、share-sheet、swiper、table、order-cancel-panel、picker、order-remark 等)。若你**自己**的页面也这样写,同理:把索引显式当 `number` 用,或改用 `getActiveNavMetricIndex` / `isItemActive(colIdx, idx)` 这类归一化函数(`re-indexes`、`re-picker` 的既有做法) |
|
|
421
|
+
| 列表 key 用 symbol 拼接告警 | UTS 类型 | 参考 `re-order-remark` 的 `getTagKey` 生成字符串 key |
|
|
422
|
+
| 模板里 ref 重复解包 | uvue 模板自动解包 | 模板中直接写 `loading`,不要写 `loading.value`(`re-luck-shake` 的修复) |
|
|
423
|
+
| 微信小程序(WebView 渲染器)不支持原生 sticky,`re-indexes` 不吸顶 | 小程序渲染层限制 | 1.0.4 起:App / H5 用原生 `list-view` + `sticky-section` 吸顶;小程序端自动回退 `scroll-view` + 普通 `view`(可滚动、可跳转、高亮联动,但暂不吸顶)。这是平台差异,不是 bug |
|
|
424
|
+
| 列数动态变化时小程序报 `Framework inner error` / 节点流错乱 | 渲染层节点流 | 1.0.5 起 `re-cascader` 改为模板内直接渲染列与选项(移除内部 `re-cascader-item`),动态列数不再错乱 |
|
|
425
|
+
| 切换上级后更深层级旧标签没清空 | 响应式 | 1.0.5 重构 `re-address` 省市区取数逻辑后已修复 |
|
|
426
|
+
| `re-video` 事件类型报错 | 类型不匹配 | 1.0.4 已修正为 `UniVideoErrorEvent` / `UniVideoTimeUpdateEvent` |
|
|
427
|
+
| `re-color-picker` 弹窗模式圆角异常 | 圆角归属 | 1.0.5:圆角由弹层容器承担,内部调色板保持直角 |
|
|
428
|
+
|
|
429
|
+
## 4. 反模式(不要这样做)
|
|
430
|
+
|
|
431
|
+
1. 跳过 `get_review_document` 直接凭「Vant/TDesign 记忆」写 props——ReView 是 uni-app x 的 `re-*` 组件,事件名(`load-more`、`update:value`)与 Web 端库并不通用。
|
|
432
|
+
2. 把文档 API 表的 `-` 当成「该组件没有 props」——应下钻 `.uvue` 的 `type Props`。
|
|
433
|
+
3. 用「应该支持」补齐文档缺失的行为(如假设 `re-cascader` 有 `v-model:value`)——文档 Events 只列了 `change` / `close` / `update:visible`,就用 `:value` + `@change`。
|
|
434
|
+
4. 引用不存在的组件名(如 `re-slide-captcha` 有源码与 readme 但**没有** `docs/components/` 页面)时,不给出文档链接、不声称文档里有该页。
|
|
435
|
+
5. 把 Web 端写法照搬:`document` / `window` / DOM 查询 / 非 flex 布局 / `npm run dev` 编译——uni-app x 全部不适用。
|
package/skills.mjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* review-skills:把 ReView 的 Skills 知识包安装到项目中的 AI 工具目录
|
|
4
|
+
*
|
|
5
|
+
* 知识包内容(`skills/review-components/`):
|
|
6
|
+
* - SKILL.md 技能入口(何时使用 + 硬性规则 + 文件导航)
|
|
7
|
+
* - WORKFLOWS.md MCP 工具工作流与完整任务示例
|
|
8
|
+
* - BEST_PRACTICES.md 安装、easycom、平台差异、主题、i18n、常见陷阱
|
|
9
|
+
* - COMPONENTS.md 按分类的组件选型清单(含 easycom 标签与文档链接)
|
|
10
|
+
*
|
|
11
|
+
* 用法:
|
|
12
|
+
* npx -y -p @reglow/review-mcp review-skills # 默认装到 .trae/skills
|
|
13
|
+
* npx -y -p @reglow/review-mcp review-skills --target cursor # 装到 .cursor/skills
|
|
14
|
+
* npx -y -p @reglow/review-mcp review-skills --target .my/skills # 自定义目录
|
|
15
|
+
* npx -y -p @reglow/review-mcp review-skills --all # 装到全部已知工具目录
|
|
16
|
+
* npx -y -p @reglow/review-mcp review-skills --list # 查看可用目标
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { isEntryModule } from "./cli-utils.mjs";
|
|
23
|
+
import { skillTargetsFromIntegrations } from "./integrations.mjs";
|
|
24
|
+
|
|
25
|
+
const PACKAGE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const SKILL_SOURCE = path.join(PACKAGE_DIR, "skills");
|
|
27
|
+
const SKILL_NAME = "review-components";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 各 AI 编程工具的 Skills 目录约定
|
|
31
|
+
*
|
|
32
|
+
* 主流工具(Trae / CodeBuddy / Qoder / Cursor / Claude)的目录来自 `integrations.mjs`
|
|
33
|
+
* 中的官方规格;Qwen Code 与 OpenAI Codex CLI 仅提供 Skills 目标。
|
|
34
|
+
*/
|
|
35
|
+
export const SKILL_TARGETS = {
|
|
36
|
+
...skillTargetsFromIntegrations(),
|
|
37
|
+
qwen: { dir: ".qwen/skills", label: "Qwen Code" },
|
|
38
|
+
codex: { dir: ".codex/skills", label: "OpenAI Codex CLI" },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** MCP 配置片段(写入客户端配置的 `mcpServers`) */
|
|
42
|
+
export function mcpConfigSnippet() {
|
|
43
|
+
return {
|
|
44
|
+
mcpServers: {
|
|
45
|
+
"review-mcp": {
|
|
46
|
+
command: "npx",
|
|
47
|
+
args: ["-y", "@reglow/review-mcp"],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function listSkillFiles(dir, base = dir) {
|
|
54
|
+
const result = [];
|
|
55
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
56
|
+
const full = path.join(dir, entry.name);
|
|
57
|
+
if (entry.isDirectory()) result.push(...listSkillFiles(full, base));
|
|
58
|
+
else if (entry.isFile()) result.push(path.relative(base, full));
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 安装 Skills 知识包
|
|
65
|
+
*
|
|
66
|
+
* @param {{ cwd?: string, targets?: string[], force?: boolean, dryRun?: boolean, logger?: (m: string) => void }} options
|
|
67
|
+
* @returns {{ installed: { target: string, dir: string, files: string[], skipped: string[] }[] }}
|
|
68
|
+
*/
|
|
69
|
+
export function installSkills(options = {}) {
|
|
70
|
+
const logger = options.logger ?? ((message) => console.log(message));
|
|
71
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
72
|
+
const force = options.force ?? false;
|
|
73
|
+
const dryRun = options.dryRun ?? false;
|
|
74
|
+
const requested = options.targets?.length ? options.targets : ["trae"];
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(SKILL_SOURCE)) {
|
|
77
|
+
throw new Error(`未找到内置 Skills 目录:${SKILL_SOURCE}`);
|
|
78
|
+
}
|
|
79
|
+
// 以技能目录为基准计算相对路径,避免安装后出现 `review-components/review-components/` 的重复层级
|
|
80
|
+
const contentDir = path.join(SKILL_SOURCE, SKILL_NAME);
|
|
81
|
+
if (!fs.existsSync(contentDir)) {
|
|
82
|
+
throw new Error(`未找到内置技能目录:${contentDir}`);
|
|
83
|
+
}
|
|
84
|
+
const sourceFiles = listSkillFiles(contentDir);
|
|
85
|
+
const installed = [];
|
|
86
|
+
|
|
87
|
+
for (const requestedTarget of requested) {
|
|
88
|
+
const preset = SKILL_TARGETS[requestedTarget];
|
|
89
|
+
const relativeDir = preset ? preset.dir : requestedTarget;
|
|
90
|
+
const targetRoot = path.resolve(cwd, relativeDir);
|
|
91
|
+
const targetDir = path.join(targetRoot, SKILL_NAME);
|
|
92
|
+
const files = [];
|
|
93
|
+
const skipped = [];
|
|
94
|
+
|
|
95
|
+
for (const file of sourceFiles) {
|
|
96
|
+
const destination = path.join(targetDir, file);
|
|
97
|
+
if (fs.existsSync(destination) && !force) {
|
|
98
|
+
skipped.push(file);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (!dryRun) {
|
|
102
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
103
|
+
fs.copyFileSync(path.join(contentDir, file), destination);
|
|
104
|
+
}
|
|
105
|
+
files.push(file);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
installed.push({ target: requestedTarget, dir: targetDir, files, skipped });
|
|
109
|
+
logger(
|
|
110
|
+
`${dryRun ? "[dry-run] " : ""}${preset?.label ?? requestedTarget} → ${path.relative(cwd, targetDir) || targetDir}` +
|
|
111
|
+
`(写入 ${files.length} 个文件${skipped.length ? `,跳过已存在 ${skipped.length} 个(--force 可覆盖)` : ""})`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { installed };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** CLI 入口 */
|
|
119
|
+
export function runSkillsCli(argv = process.argv.slice(2)) {
|
|
120
|
+
if (argv.includes("--list")) {
|
|
121
|
+
console.log("可用目标(--target 支持预设名或自定义目录):");
|
|
122
|
+
for (const [key, value] of Object.entries(SKILL_TARGETS)) {
|
|
123
|
+
console.log(` ${key.padEnd(10)} ${value.dir.padEnd(18)} ${value.label}`);
|
|
124
|
+
}
|
|
125
|
+
console.log("\n示例:npx -y -p @reglow/review-mcp review-skills --target trae");
|
|
126
|
+
console.log(
|
|
127
|
+
"需要同时写 MCP 配置:npx -y -p @reglow/review-mcp review-setup --tools trae,codebuddy,qoder",
|
|
128
|
+
);
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const values = { target: undefined, cwd: undefined };
|
|
133
|
+
const flags = { all: false, force: false, dryRun: false };
|
|
134
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
135
|
+
const arg = argv[i];
|
|
136
|
+
if (arg === "--all") flags.all = true;
|
|
137
|
+
else if (arg === "--force") flags.force = true;
|
|
138
|
+
else if (arg === "--dry-run") flags.dryRun = true;
|
|
139
|
+
else if (arg === "--target" || arg === "-t") values.target = argv[++i];
|
|
140
|
+
else if (arg.startsWith("--target="))
|
|
141
|
+
values.target = arg.slice("--target=".length);
|
|
142
|
+
else if (arg === "--cwd") values.cwd = argv[++i];
|
|
143
|
+
else if (arg.startsWith("--cwd=")) values.cwd = arg.slice("--cwd=".length);
|
|
144
|
+
else if (arg === "--help" || arg === "-h") {
|
|
145
|
+
console.log(
|
|
146
|
+
[
|
|
147
|
+
"review-skills —— 安装 ReView 的 Agent Skills 知识包",
|
|
148
|
+
"",
|
|
149
|
+
"用法:npx -y -p @reglow/review-mcp review-skills [选项]",
|
|
150
|
+
"",
|
|
151
|
+
"选项:",
|
|
152
|
+
" -t, --target <name|dir> 安装目标:预设名(trae/codebuddy/qoder/cursor/claude/qwen/codex/generic)或自定义目录,默认 trae",
|
|
153
|
+
" --all 安装到全部预设目录",
|
|
154
|
+
" --cwd <dir> 项目根目录,默认当前目录",
|
|
155
|
+
" --force 覆盖已存在的文件",
|
|
156
|
+
" --dry-run 只打印将要写入的文件",
|
|
157
|
+
" --list 列出可用目标",
|
|
158
|
+
" -h, --help 显示帮助",
|
|
159
|
+
"",
|
|
160
|
+
"提示:需要同时写入 MCP 配置时,用 review-setup(等价于 review-mcp --setup)。",
|
|
161
|
+
].join("\n"),
|
|
162
|
+
);
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const targets = flags.all
|
|
168
|
+
? Object.keys(SKILL_TARGETS)
|
|
169
|
+
: [values.target ?? "trae"];
|
|
170
|
+
const result = installSkills({
|
|
171
|
+
cwd: values.cwd,
|
|
172
|
+
targets,
|
|
173
|
+
force: flags.force,
|
|
174
|
+
dryRun: flags.dryRun,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const total = result.installed.reduce((sum, item) => sum + item.files.length, 0);
|
|
178
|
+
console.log(`\n已安装 ReView Skills:${SKILL_NAME}(${total} 个文件)`);
|
|
179
|
+
console.log("\n配套的 MCP 客户端配置(mcpServers):");
|
|
180
|
+
console.log(JSON.stringify(mcpConfigSnippet(), null, 2));
|
|
181
|
+
console.log(
|
|
182
|
+
"\n提示:Skills 负责「怎么用」,MCP 负责「查得到」——建议两者一起配置。",
|
|
183
|
+
);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (isEntryModule(import.meta.url)) {
|
|
188
|
+
try {
|
|
189
|
+
process.exitCode = runSkillsCli();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(`[review-skills] 安装失败:${error.message}`);
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
}
|
|
194
|
+
}
|