@fredwsw/frontend-rules 1.0.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 +302 -0
- package/bin/cli.js +155 -0
- package/package.json +22 -0
- package/template/docs/uv-ui-docs/action-sheet-cn.md +180 -0
- package/template/docs/uv-ui-docs/album-cn.md +104 -0
- package/template/docs/uv-ui-docs/alert-cn.md +71 -0
- package/template/docs/uv-ui-docs/avatar-cn.md +149 -0
- package/template/docs/uv-ui-docs/back-top-cn.md +121 -0
- package/template/docs/uv-ui-docs/badge-cn.md +100 -0
- package/template/docs/uv-ui-docs/button-cn.md +181 -0
- package/template/docs/uv-ui-docs/calendar-cn.md +179 -0
- package/template/docs/uv-ui-docs/calendars-cn.md +264 -0
- package/template/docs/uv-ui-docs/cell-cn.md +163 -0
- package/template/docs/uv-ui-docs/checkbox-cn.md +164 -0
- package/template/docs/uv-ui-docs/code-cn.md +122 -0
- package/template/docs/uv-ui-docs/code-input-cn.md +127 -0
- package/template/docs/uv-ui-docs/collapse-cn.md +206 -0
- package/template/docs/uv-ui-docs/color-cn.md +122 -0
- package/template/docs/uv-ui-docs/count-down-cn.md +169 -0
- package/template/docs/uv-ui-docs/count-to-cn.md +125 -0
- package/template/docs/uv-ui-docs/datetime-picker-cn.md +167 -0
- package/template/docs/uv-ui-docs/divider-cn.md +90 -0
- package/template/docs/uv-ui-docs/drop-down-cn.md +287 -0
- package/template/docs/uv-ui-docs/empty-cn.md +76 -0
- package/template/docs/uv-ui-docs/form-cn.md +220 -0
- package/template/docs/uv-ui-docs/gap-cn.md +63 -0
- package/template/docs/uv-ui-docs/grid-cn.md +187 -0
- package/template/docs/uv-ui-docs/icon-cn.md +255 -0
- package/template/docs/uv-ui-docs/image-cn.md +170 -0
- package/template/docs/uv-ui-docs/index-list-cn.md +132 -0
- package/template/docs/uv-ui-docs/input-cn.md +157 -0
- package/template/docs/uv-ui-docs/keyboard-cn.md +156 -0
- package/template/docs/uv-ui-docs/layout-cn.md +215 -0
- package/template/docs/uv-ui-docs/line-cn.md +77 -0
- package/template/docs/uv-ui-docs/line-progress-cn.md +132 -0
- package/template/docs/uv-ui-docs/link-cn.md +97 -0
- package/template/docs/uv-ui-docs/list-cn.md +185 -0
- package/template/docs/uv-ui-docs/load-more-cn.md +144 -0
- package/template/docs/uv-ui-docs/loading-icon-cn.md +125 -0
- package/template/docs/uv-ui-docs/loading-page-cn.md +85 -0
- package/template/docs/uv-ui-docs/modal-cn.md +212 -0
- package/template/docs/uv-ui-docs/navbar-cn.md +171 -0
- package/template/docs/uv-ui-docs/no-network-cn.md +112 -0
- package/template/docs/uv-ui-docs/notice-bar-cn.md +119 -0
- package/template/docs/uv-ui-docs/notify-cn.md +185 -0
- package/template/docs/uv-ui-docs/number-box-cn.md +131 -0
- package/template/docs/uv-ui-docs/overlay-cn.md +122 -0
- package/template/docs/uv-ui-docs/parse-cn.md +177 -0
- package/template/docs/uv-ui-docs/pick-color-cn.md +147 -0
- package/template/docs/uv-ui-docs/picker-cn.md +159 -0
- package/template/docs/uv-ui-docs/popup-cn.md +184 -0
- package/template/docs/uv-ui-docs/qrcode-cn.md +150 -0
- package/template/docs/uv-ui-docs/radio-cn.md +166 -0
- package/template/docs/uv-ui-docs/rate-cn.md +91 -0
- package/template/docs/uv-ui-docs/read-more-cn.md +191 -0
- package/template/docs/uv-ui-docs/scroll-list-cn.md +188 -0
- package/template/docs/uv-ui-docs/search-cn.md +107 -0
- package/template/docs/uv-ui-docs/skeleton-cn.md +101 -0
- package/template/docs/uv-ui-docs/skeletons-cn.md +312 -0
- package/template/docs/uv-ui-docs/slider-cn.md +98 -0
- package/template/docs/uv-ui-docs/steps-cn.md +158 -0
- package/template/docs/uv-ui-docs/sticky-cn.md +82 -0
- package/template/docs/uv-ui-docs/subsection-cn.md +161 -0
- package/template/docs/uv-ui-docs/swipe-action-cn.md +207 -0
- package/template/docs/uv-ui-docs/swiper-cn.md +247 -0
- package/template/docs/uv-ui-docs/switch-cn.md +136 -0
- package/template/docs/uv-ui-docs/tabbar-cn.md +224 -0
- package/template/docs/uv-ui-docs/tabs-cn.md +260 -0
- package/template/docs/uv-ui-docs/tags-cn.md +191 -0
- package/template/docs/uv-ui-docs/text-cn.md +178 -0
- package/template/docs/uv-ui-docs/textarea-cn.md +132 -0
- package/template/docs/uv-ui-docs/toast-cn.md +110 -0
- package/template/docs/uv-ui-docs/tooltip-cn.md +91 -0
- package/template/docs/uv-ui-docs/transition-cn.md +202 -0
- package/template/docs/uv-ui-docs/upload-cn.md +156 -0
- package/template/docs/uv-ui-docs/vtabs-cn.md +190 -0
- package/template/docs/uv-ui-docs/waterfall-cn.md +276 -0
- package/template/rules/common.mdc +90 -0
- package/template/rules/uv-ui-docs.mdc +111 -0
- package/template/skills/code-simplifier/SKILL.md +109 -0
- package/template/skills/frontend-design/SKILL.md +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# `@sanshui/frontend-rules` 使用说明
|
|
2
|
+
|
|
3
|
+
本文档说明如何把当前仓库的规则模板打包成一个可执行 npm 包,并通过 `pnpm dlx` 在其他项目一键同步到项目根目录。
|
|
4
|
+
|
|
5
|
+
## 目标
|
|
6
|
+
|
|
7
|
+
- 发布一个包:`@sanshui/frontend-rules`
|
|
8
|
+
- 在任意项目根目录执行:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm dlx @sanshui/frontend-rules@latest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- 自动将包内模板按需同步到当前项目根目录:
|
|
15
|
+
- 规则和技能同步到 `.cursor`
|
|
16
|
+
- 组件库文档同步到 `docs`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 一、建议的包结构
|
|
21
|
+
|
|
22
|
+
新建一个用于发布的仓库(或就在当前仓库整理),结构建议如下:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
frontend-rules/
|
|
26
|
+
bin/
|
|
27
|
+
cli.js
|
|
28
|
+
template/
|
|
29
|
+
rules/
|
|
30
|
+
common.mdc
|
|
31
|
+
uv-ui-docs.mdc
|
|
32
|
+
...
|
|
33
|
+
docs/
|
|
34
|
+
uv-ui-docs/
|
|
35
|
+
...
|
|
36
|
+
skills/
|
|
37
|
+
...
|
|
38
|
+
package.json
|
|
39
|
+
README.md
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
说明:
|
|
43
|
+
|
|
44
|
+
- `template/rules`:规则文件模板。
|
|
45
|
+
- `template/docs`:组件库文档模板,按用户选择复制。
|
|
46
|
+
- `template/skills`:技能模板。
|
|
47
|
+
- `bin/cli.js`:`pnpm dlx` 实际执行入口。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 二、`package.json` 示例
|
|
52
|
+
|
|
53
|
+
> 包名改成你的正式名:`@sanshui/frontend-rules`
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"name": "@sanshui/frontend-rules",
|
|
58
|
+
"version": "1.0.0",
|
|
59
|
+
"description": "Sync latest .cursor rules into project root",
|
|
60
|
+
"type": "module",
|
|
61
|
+
"bin": {
|
|
62
|
+
"frontend-rules": "./bin/cli.js"
|
|
63
|
+
},
|
|
64
|
+
"files": [
|
|
65
|
+
"bin",
|
|
66
|
+
"template",
|
|
67
|
+
"README.md"
|
|
68
|
+
],
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=18"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@inquirer/prompts": "^7.8.4",
|
|
74
|
+
"fs-extra": "^11.2.0"
|
|
75
|
+
},
|
|
76
|
+
"license": "MIT"
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 三、`bin/cli.js` 示例
|
|
83
|
+
|
|
84
|
+
> 作用:基础规则与技能始终复制到 `.cursor`,组件库文档支持交互多选后按需复制到项目根目录 `docs`。支持 `--dry-run`。
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
#!/usr/bin/env node
|
|
88
|
+
import path from "node:path";
|
|
89
|
+
import process from "node:process";
|
|
90
|
+
import { fileURLToPath } from "node:url";
|
|
91
|
+
import fs from "fs-extra";
|
|
92
|
+
import { checkbox } from "@inquirer/prompts";
|
|
93
|
+
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
96
|
+
const __dirname = path.dirname(__filename);
|
|
97
|
+
const root = path.resolve(__dirname, "..");
|
|
98
|
+
const sourceDir = path.join(root, "template");
|
|
99
|
+
const cursorTargetDir = path.join(cwd, ".cursor");
|
|
100
|
+
const docsTargetRootDir = path.join(cwd, "docs");
|
|
101
|
+
const args = new Set(process.argv.slice(2));
|
|
102
|
+
const dryRun = args.has("--dry-run");
|
|
103
|
+
const allLibs = args.has("--all-libs");
|
|
104
|
+
const libsArg = [...args].find((arg) => arg.startsWith("--libs="));
|
|
105
|
+
|
|
106
|
+
function parseLibsArg() {
|
|
107
|
+
if (!libsArg) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const value = libsArg.slice("--libs=".length).trim();
|
|
111
|
+
if (!value) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function getLibraryOptions() {
|
|
118
|
+
const docsRoot = path.join(sourceDir, "docs");
|
|
119
|
+
const exists = await fs.pathExists(docsRoot);
|
|
120
|
+
if (!exists) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const entries = await fs.readdir(docsRoot, { withFileTypes: true });
|
|
124
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => {
|
|
125
|
+
const docsDirName = entry.name;
|
|
126
|
+
return {
|
|
127
|
+
key: docsDirName.replace(/-docs$/i, ""),
|
|
128
|
+
docsDirName,
|
|
129
|
+
docsSourcePath: path.join(docsRoot, docsDirName),
|
|
130
|
+
ruleFileName: `${docsDirName}.mdc`,
|
|
131
|
+
ruleSourcePath: path.join(sourceDir, "rules", `${docsDirName}.mdc`),
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function promptLibraries(options) {
|
|
137
|
+
if (options.length === 0) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
if (!process.stdin.isTTY) {
|
|
141
|
+
return options;
|
|
142
|
+
}
|
|
143
|
+
const selectedKeys = await checkbox({
|
|
144
|
+
message: "请选择组件库(方向键移动,空格勾选,回车确认)",
|
|
145
|
+
choices: options.map((option) => ({ name: option.key, value: option.key })),
|
|
146
|
+
});
|
|
147
|
+
if (!selectedKeys || selectedKeys.length === 0) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
return options.filter((option) => new Set(selectedKeys).has(option.key));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function resolveSelectedLibraries(options) {
|
|
154
|
+
if (allLibs) {
|
|
155
|
+
return options;
|
|
156
|
+
}
|
|
157
|
+
const parsedLibKeys = parseLibsArg();
|
|
158
|
+
if (parsedLibKeys !== null) {
|
|
159
|
+
return options.filter((option) => new Set(parsedLibKeys).has(option.key));
|
|
160
|
+
}
|
|
161
|
+
return promptLibraries(options);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function copyWithDryRun(from, to) {
|
|
165
|
+
if (dryRun) {
|
|
166
|
+
console.log("[frontend-rules] copy:", from, "->", to);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
await fs.copy(from, to, { overwrite: true, recursive: true });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function main() {
|
|
173
|
+
await fs.ensureDir(cursorTargetDir);
|
|
174
|
+
await fs.ensureDir(docsTargetRootDir);
|
|
175
|
+
|
|
176
|
+
await copyWithDryRun(path.join(sourceDir, "rules", "common.mdc"), path.join(cursorTargetDir, "rules", "common.mdc"));
|
|
177
|
+
await copyWithDryRun(path.join(sourceDir, "skills"), path.join(cursorTargetDir, "skills"));
|
|
178
|
+
|
|
179
|
+
const options = await getLibraryOptions();
|
|
180
|
+
const selectedLibraries = await resolveSelectedLibraries(options);
|
|
181
|
+
for (const library of selectedLibraries) {
|
|
182
|
+
await copyWithDryRun(library.docsSourcePath, path.join(docsTargetRootDir, library.docsDirName));
|
|
183
|
+
if (await fs.pathExists(library.ruleSourcePath)) {
|
|
184
|
+
await copyWithDryRun(library.ruleSourcePath, path.join(cursorTargetDir, "rules", library.ruleFileName));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
main();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 四、本地联调
|
|
195
|
+
|
|
196
|
+
在包仓库目录执行:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
pnpm install
|
|
200
|
+
node ./bin/cli.js --dry-run
|
|
201
|
+
node ./bin/cli.js
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
然后检查当前目录是否生成/更新了:
|
|
205
|
+
|
|
206
|
+
- `.cursor/rules`
|
|
207
|
+
- `.cursor/skills`
|
|
208
|
+
- `docs/<组件库>-docs`
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 五、发布到 npm
|
|
213
|
+
|
|
214
|
+
1. 登录 npm(首次)
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
npm login
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
2. 发布(Scoped 包通常建议公开)
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm publish --access public
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
3. 后续更新流程
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# 修改 template/.cursor 内容后
|
|
230
|
+
npm version patch
|
|
231
|
+
npm publish --access public
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 六、在其他项目中使用
|
|
237
|
+
|
|
238
|
+
进入目标项目根目录,执行:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
pnpm dlx @sanshui/frontend-rules@latest
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
常用变体:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# 只预览,不落盘(若你实现了 --dry-run 参数)
|
|
248
|
+
pnpm dlx @sanshui/frontend-rules@latest --dry-run
|
|
249
|
+
|
|
250
|
+
# 不交互,直接选择全部组件库
|
|
251
|
+
pnpm dlx @sanshui/frontend-rules@latest --all-libs
|
|
252
|
+
|
|
253
|
+
# 不交互,指定组件库(可多选,逗号分隔)
|
|
254
|
+
pnpm dlx @sanshui/frontend-rules@latest --libs=uv-ui,first-ui
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 七、推荐约定(团队协作)
|
|
260
|
+
|
|
261
|
+
- 把 `.cursor` 规则维护统一放在 `@sanshui/frontend-rules`,业务仓库不各自散改。
|
|
262
|
+
- 每次规则更新都发版,业务仓库按需执行 `pnpm dlx ...@latest` 同步。
|
|
263
|
+
- 若业务仓库存在少量本地特化,可约定放在单独文件并在同步后手动合并。
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 八、常见问题
|
|
268
|
+
|
|
269
|
+
### 1)`pnpm dlx` 没拿到最新版本?
|
|
270
|
+
|
|
271
|
+
- 显式指定 `@latest`:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pnpm dlx @sanshui/frontend-rules@latest
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
- 确认刚发布版本已在 npm 可见。
|
|
278
|
+
|
|
279
|
+
### 2)会不会覆盖本地 `.cursor`?
|
|
280
|
+
|
|
281
|
+
- 当前示例是覆盖策略(`overwrite: true`)。
|
|
282
|
+
- 如果你想保守一点,可扩展参数:
|
|
283
|
+
- `--force` 才覆盖
|
|
284
|
+
- 默认仅拷贝不存在文件
|
|
285
|
+
- 或执行前先备份到 `.cursor.bak-时间戳`
|
|
286
|
+
|
|
287
|
+
### 3)如何控制复制哪些组件库文档?
|
|
288
|
+
|
|
289
|
+
- 交互模式下支持方向键上下移动、空格勾选、回车确认(基于 `@inquirer/prompts`)。
|
|
290
|
+
- 非交互可用 `--libs=uv-ui,first-ui`。
|
|
291
|
+
- 全量组件库可用 `--all-libs`。
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 九、最小执行清单
|
|
296
|
+
|
|
297
|
+
1. 准备 `template/rules`、`template/docs`、`template/skills` 模板目录。
|
|
298
|
+
2. 写好 `bin/cli.js` 复制逻辑。
|
|
299
|
+
3. 配置 `package.json` 的 `name`、`bin`、`files`。
|
|
300
|
+
4. `npm publish --access public`。
|
|
301
|
+
5. 在业务项目运行:`pnpm dlx @sanshui/frontend-rules@latest`,按提示多选组件库。
|
|
302
|
+
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import { checkbox } from "@inquirer/prompts";
|
|
7
|
+
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const root = path.resolve(__dirname, "..");
|
|
12
|
+
const sourceDir = path.join(root, "template");
|
|
13
|
+
const cursorTargetDir = path.join(cwd, ".cursor");
|
|
14
|
+
const docsTargetRootDir = path.join(cwd, "docs");
|
|
15
|
+
const args = new Set(process.argv.slice(2));
|
|
16
|
+
const dryRun = args.has("--dry-run");
|
|
17
|
+
const allLibs = args.has("--all-libs");
|
|
18
|
+
const libsArg = [...args].find((arg) => arg.startsWith("--libs="));
|
|
19
|
+
|
|
20
|
+
function parseLibsArg() {
|
|
21
|
+
if (!libsArg) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const value = libsArg.slice("--libs=".length).trim();
|
|
25
|
+
if (!value) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return value
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((item) => item.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function getLibraryOptions() {
|
|
35
|
+
const docsRoot = path.join(sourceDir, "docs");
|
|
36
|
+
const exists = await fs.pathExists(docsRoot);
|
|
37
|
+
if (!exists) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const entries = await fs.readdir(docsRoot, { withFileTypes: true });
|
|
42
|
+
return entries
|
|
43
|
+
.filter((entry) => entry.isDirectory())
|
|
44
|
+
.map((entry) => {
|
|
45
|
+
const docsDirName = entry.name;
|
|
46
|
+
const docsSourcePath = path.join(docsRoot, docsDirName);
|
|
47
|
+
const ruleFileName = `${docsDirName}.mdc`;
|
|
48
|
+
const ruleSourcePath = path.join(sourceDir, "rules", ruleFileName);
|
|
49
|
+
const key = docsDirName.replace(/-docs$/i, "");
|
|
50
|
+
return {
|
|
51
|
+
key,
|
|
52
|
+
docsDirName,
|
|
53
|
+
docsSourcePath,
|
|
54
|
+
ruleFileName,
|
|
55
|
+
ruleSourcePath,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function promptLibraries(options) {
|
|
61
|
+
if (options.length === 0) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!process.stdin.isTTY) {
|
|
66
|
+
console.log("[frontend-rules] 非交互环境,默认选择全部组件库。可用 --libs=... 精确指定。");
|
|
67
|
+
return options;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const selectedKeys = await checkbox({
|
|
71
|
+
message: "请选择组件库(方向键移动,空格勾选,回车确认)",
|
|
72
|
+
choices: options.map((option) => ({
|
|
73
|
+
name: option.key,
|
|
74
|
+
value: option.key,
|
|
75
|
+
})),
|
|
76
|
+
});
|
|
77
|
+
if (!selectedKeys || selectedKeys.length === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const selectedSet = new Set(selectedKeys);
|
|
81
|
+
return options.filter((option) => selectedSet.has(option.key));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function resolveSelectedLibraries(options) {
|
|
85
|
+
if (allLibs) {
|
|
86
|
+
return options;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parsedLibKeys = parseLibsArg();
|
|
90
|
+
if (parsedLibKeys !== null) {
|
|
91
|
+
const keySet = new Set(parsedLibKeys);
|
|
92
|
+
return options.filter((option) => keySet.has(option.key));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return promptLibraries(options);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function copyWithDryRun(from, to) {
|
|
99
|
+
if (dryRun) {
|
|
100
|
+
console.log("[frontend-rules] copy:", from, "->", to);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await fs.copy(from, to, { overwrite: true, recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function main() {
|
|
107
|
+
const exists = await fs.pathExists(sourceDir);
|
|
108
|
+
if (!exists) {
|
|
109
|
+
console.error("[frontend-rules] 模板目录不存在:", sourceDir);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (dryRun) {
|
|
114
|
+
console.log("[frontend-rules] dry-run 模式,不写入文件");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const libraryOptions = await getLibraryOptions();
|
|
118
|
+
const selectedLibraries = await resolveSelectedLibraries(libraryOptions);
|
|
119
|
+
|
|
120
|
+
await fs.ensureDir(cursorTargetDir);
|
|
121
|
+
|
|
122
|
+
const commonRuleSource = path.join(sourceDir, "rules", "common.mdc");
|
|
123
|
+
const commonRuleTarget = path.join(cursorTargetDir, "rules", "common.mdc");
|
|
124
|
+
const skillsSource = path.join(sourceDir, "skills");
|
|
125
|
+
const skillsTarget = path.join(cursorTargetDir, "skills");
|
|
126
|
+
|
|
127
|
+
await copyWithDryRun(commonRuleSource, commonRuleTarget);
|
|
128
|
+
await copyWithDryRun(skillsSource, skillsTarget);
|
|
129
|
+
|
|
130
|
+
if (selectedLibraries.length > 0) {
|
|
131
|
+
await fs.ensureDir(docsTargetRootDir);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const library of selectedLibraries) {
|
|
135
|
+
const docsTargetPath = path.join(docsTargetRootDir, library.docsDirName);
|
|
136
|
+
await copyWithDryRun(library.docsSourcePath, docsTargetPath);
|
|
137
|
+
|
|
138
|
+
const ruleExists = await fs.pathExists(library.ruleSourcePath);
|
|
139
|
+
if (ruleExists) {
|
|
140
|
+
const ruleTargetPath = path.join(cursorTargetDir, "rules", library.ruleFileName);
|
|
141
|
+
await copyWithDryRun(library.ruleSourcePath, ruleTargetPath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const selectedKeys = selectedLibraries.map((item) => item.key).join(", ") || "无";
|
|
146
|
+
console.log("[frontend-rules] 已同步基础规则与技能目录。");
|
|
147
|
+
console.log("[frontend-rules] 已选择组件库:", selectedKeys);
|
|
148
|
+
console.log("[frontend-rules] .cursor 目标目录:", cursorTargetDir);
|
|
149
|
+
console.log("[frontend-rules] docs 目标目录:", docsTargetRootDir);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main().catch((err) => {
|
|
153
|
+
console.error("[frontend-rules] 执行失败:", err);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fredwsw/frontend-rules",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Sync latest .cursor rules into project root",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"frontend-rules": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"template",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@inquirer/prompts": "^7.8.4",
|
|
19
|
+
"fs-extra": "^11.2.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
group: 反馈
|
|
3
|
+
category: Components
|
|
4
|
+
title: ActionSheet
|
|
5
|
+
subtitle: 操作菜单
|
|
6
|
+
description: 从底部弹出操作菜单供用户选择,功能类似 uni.showActionSheet,提供更灵活的配置与统一的多端表现。
|
|
7
|
+
demo:
|
|
8
|
+
cols: 2
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 何时使用 {#when-to-use}
|
|
12
|
+
|
|
13
|
+
`uv-action-sheet` 用于 **从底部弹出一组操作选项** 的场景:
|
|
14
|
+
|
|
15
|
+
- 替代 `uni.showActionSheet`,统一多端表现
|
|
16
|
+
- 需要带描述、副标题、禁用状态、加载状态、开放能力等更复杂选项配置
|
|
17
|
+
- 与弹窗、操作确认、分享面板等组合使用
|
|
18
|
+
|
|
19
|
+
> 完整示例下载地址:[uv-ui 插件市场](https://ext.dcloud.net.cn/plugin?name=uv-ui)
|
|
20
|
+
|
|
21
|
+
## 平台兼容性 {#platform}
|
|
22
|
+
|
|
23
|
+
| App(vue) | App(nvue) | H5 | 小程序 | VUE2 | VUE3 |
|
|
24
|
+
| --- | --- | --- | --- | --- | --- |
|
|
25
|
+
| √ | √ | √ | √ | √ | √ |
|
|
26
|
+
|
|
27
|
+
**组件名:** `uv-action-sheet`
|
|
28
|
+
|
|
29
|
+
## 代码演示 {#examples}
|
|
30
|
+
|
|
31
|
+
### 基本使用 {#basic}
|
|
32
|
+
|
|
33
|
+
通过 `title`、`cancelText`、`description` 配置标题、取消按钮文案、顶部描述;
|
|
34
|
+
通过 `actions` 配置选项数组,使用 `ref` 调用 `open()` 打开。
|
|
35
|
+
|
|
36
|
+
```vue
|
|
37
|
+
<template>
|
|
38
|
+
<view>
|
|
39
|
+
<uv-action-sheet
|
|
40
|
+
ref="actionSheet"
|
|
41
|
+
:actions="list"
|
|
42
|
+
title="标题"
|
|
43
|
+
@select="select"
|
|
44
|
+
@close="close"
|
|
45
|
+
></uv-action-sheet>
|
|
46
|
+
|
|
47
|
+
<button @click="open">打开ActionSheet</button>
|
|
48
|
+
</view>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script>
|
|
52
|
+
export default {
|
|
53
|
+
data() {
|
|
54
|
+
return {
|
|
55
|
+
list: [
|
|
56
|
+
{
|
|
57
|
+
name: '选项一',
|
|
58
|
+
subname: '选项一描述',
|
|
59
|
+
color: '#ffaa7f',
|
|
60
|
+
fontSize: '20'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: '选项二禁用',
|
|
64
|
+
disabled: true
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: '开启load加载',
|
|
68
|
+
loading: true
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
methods: {
|
|
74
|
+
open() {
|
|
75
|
+
this.$refs.actionSheet.open()
|
|
76
|
+
},
|
|
77
|
+
select(e) {
|
|
78
|
+
console.log('选中该项:', e)
|
|
79
|
+
},
|
|
80
|
+
close() {
|
|
81
|
+
console.log('关闭')
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 控制遮罩及点击项关闭行为 {#close-control}
|
|
89
|
+
|
|
90
|
+
通过 `closeOnClickOverlay` 和 `closeOnClickAction` 分别控制 **点击遮罩**、**点击菜单项** 时是否关闭弹窗。
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<uv-action-sheet
|
|
94
|
+
ref="actionSheet"
|
|
95
|
+
:actions="list"
|
|
96
|
+
title="标题"
|
|
97
|
+
:closeOnClickOverlay="false"
|
|
98
|
+
:closeOnClickAction="false"
|
|
99
|
+
@select="select"
|
|
100
|
+
@close="close"
|
|
101
|
+
></uv-action-sheet>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 选项开放能力(小程序) {#open-type}
|
|
105
|
+
|
|
106
|
+
结合小程序 `button` 的开放能力使用,例如分享、获取用户信息等。
|
|
107
|
+
|
|
108
|
+
```vue
|
|
109
|
+
<uv-action-sheet
|
|
110
|
+
ref="actionSheet"
|
|
111
|
+
:actions="list"
|
|
112
|
+
title="开放能力"
|
|
113
|
+
@select="select"
|
|
114
|
+
@close="close"
|
|
115
|
+
></uv-action-sheet>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`actions` 中可设置:
|
|
119
|
+
|
|
120
|
+
- `openType: 'share'`
|
|
121
|
+
- `openType: 'getUserInfo'`
|
|
122
|
+
- `openType: 'contact'`
|
|
123
|
+
|
|
124
|
+
等。
|
|
125
|
+
|
|
126
|
+
## API {#api}
|
|
127
|
+
|
|
128
|
+
### ActionSheet Props {#props}
|
|
129
|
+
|
|
130
|
+
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|
|
131
|
+
| --- | --- | --- | --- | --- |
|
|
132
|
+
| title | 标题 | String | - | - |
|
|
133
|
+
| description | 选项上方的描述信息 | String | - | - |
|
|
134
|
+
| actions | 选项配置数组,见下方「Actions Props」 | Array\<Object> | [] | - |
|
|
135
|
+
| cancelText | 取消按钮文字(非空时显示取消按钮) | String | - | - |
|
|
136
|
+
| safeAreaInsetBottom | 是否开启底部安全区适配 | Boolean | false | true \| false |
|
|
137
|
+
| closeOnClickOverlay | 点击遮罩是否允许关闭组件 | Boolean | - | - |
|
|
138
|
+
| closeOnClickAction | 点击某个菜单项时是否关闭组件 | String | true | true \| false |
|
|
139
|
+
| round | 菜单面板圆角值 | String \| Number | 0 | - |
|
|
140
|
+
| lang | 返回用户信息的语言(小程序) | String | en | en \| zh_CN \| zh_TW |
|
|
141
|
+
| sessionFrom | 会话来源(`open-type="contact"` 有效,仅微信小程序) | String | - | - |
|
|
142
|
+
| sendMessageTitle | 会话内消息卡片标题 | String | - | - |
|
|
143
|
+
| sendMessagePath | 会话内消息卡片跳转路径 | String | - | - |
|
|
144
|
+
| sendMessageImg | 会话内消息卡片图片 | String | - | - |
|
|
145
|
+
| showMessageCard | 是否显示会话内消息卡片 | Boolean | false | true \| false |
|
|
146
|
+
| appParameter | 打开 APP 时传递的参数,`openType="launchApp"` 有效 | String | - | - |
|
|
147
|
+
|
|
148
|
+
### Actions Props {#actions-props}
|
|
149
|
+
|
|
150
|
+
单个 `actions` 元素支持以下字段:
|
|
151
|
+
|
|
152
|
+
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
|
|
153
|
+
| --- | --- | --- | --- | --- |
|
|
154
|
+
| openType | 小程序开放能力类型 | String | '' | - |
|
|
155
|
+
| disabled | 是否禁用该菜单 | Boolean | false | true \| false |
|
|
156
|
+
| loading | 是否显示加载状态(开启后文字不显示) | Boolean | false | true \| false |
|
|
157
|
+
| name | 菜单标题 | String | '' | - |
|
|
158
|
+
| subname | 菜单描述 | String | '' | - |
|
|
159
|
+
| fontSize | `name` 字体大小(px/rpx) | String | 16px | - |
|
|
160
|
+
| color | `name` 字体颜色 | String | #303133 | - |
|
|
161
|
+
|
|
162
|
+
### Methods {#methods}
|
|
163
|
+
|
|
164
|
+
| 方法名 | 说明 |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| open | 打开操作菜单 |
|
|
167
|
+
| close | 关闭操作菜单 |
|
|
168
|
+
|
|
169
|
+
### Events {#events}
|
|
170
|
+
|
|
171
|
+
| 事件名 | 说明 | 回调参数 |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| @select | 点击 ActionSheet 列表项时触发 | 选项对象 |
|
|
174
|
+
| @close | 点击取消按钮时触发;若需点击遮罩触发,需设置 `closeOnClickOverlay="true"` | - |
|
|
175
|
+
| @getuserinfo | 获取用户信息回调,`openType="getUserInfo"` 有效 | detail |
|
|
176
|
+
| @contact | 客服消息回调,`openType="contact"` 有效 | - |
|
|
177
|
+
| @getphonenumber | 获取用户手机号回调,`openType="getPhoneNumber"` 有效 | - |
|
|
178
|
+
| @error | 使用开放能力出错时回调,`openType="error"` 有效 | - |
|
|
179
|
+
| @launchapp | 打开 APP 成功回调,`openType="launchApp"` 有效 | - |
|
|
180
|
+
| @opensetting | 打开授权设置页后回调,`openType="openSetting"` 有效 | - |
|