listpage_cli 0.0.309 → 0.0.311
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/bin/adapters/cli-interaction.js +5 -7
- package/bin/app/parse-args.js +16 -7
- package/bin/cli.js +6 -14
- package/bin/commands/deploy-project-command.js +2 -2
- package/bin/commands/lark/parse-doc.js +16 -21
- package/bin/commands/project-command.js +25 -0
- package/bin/commands/release-project-command.js +2 -2
- package/bin/services/build-project-service.js +26 -3
- package/package.json +1 -1
- package/templates/backend-template/package.json.tmpl +1 -1
- package/templates/frontend-template/package.json.tmpl +2 -2
- package/templates/skills-template/listpage-filter-group/SKILL.md +33 -0
- package/templates/skills-template/listpage-filter-group/api.md +46 -0
- package/templates/skills-template/listpage-filter-group/examples.md +82 -0
|
@@ -160,17 +160,15 @@ function printHelp() {
|
|
|
160
160
|
` 用法: ${helpColor("listpage_cli install-skill [skillName] [--project]", "dim")}`,
|
|
161
161
|
" 说明: 安装技能到 Cursor;默认 skillName 为 test,安装到当前命令执行目录的 .cursor/skills/",
|
|
162
162
|
"",
|
|
163
|
-
` ${helpColor("
|
|
164
|
-
` 用法: ${helpColor("listpage_cli build
|
|
165
|
-
" 说明: 非交互校验当前目录是否为有效项目根(需存在 listpage.config.json
|
|
163
|
+
` ${helpColor("project", "green")}`,
|
|
164
|
+
` 用法: ${helpColor("listpage_cli project build", "dim")}`,
|
|
165
|
+
" 说明: 非交互校验当前目录是否为有效项目根(需存在 listpage.config.json),构建并写入 .listpage/output",
|
|
166
166
|
"",
|
|
167
|
-
`
|
|
168
|
-
` 用法: ${helpColor("listpage_cli release-project [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE]", "dim")}`,
|
|
167
|
+
` 用法: ${helpColor("listpage_cli project release [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE]", "dim")}`,
|
|
169
168
|
" 说明: 先校验 .listpage/output 产物,再按 login/build/tag/push 执行 Docker 发布",
|
|
170
169
|
` 备注: ${helpColor("参数优先级为 CLI > profile > base", "yellow")}`,
|
|
171
170
|
"",
|
|
172
|
-
`
|
|
173
|
-
` 用法: ${helpColor("listpage_cli deploy-project [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE] [--skip-image]", "dim")}`,
|
|
171
|
+
` 用法: ${helpColor("listpage_cli project deploy [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE] [--skip-image]", "dim")}`,
|
|
174
172
|
" 说明: 使用 docker.remote + docker.container 执行部署,支持 ports[] 与 envFile/env 合并",
|
|
175
173
|
` 备注: ${helpColor("默认会清理并拉取镜像;传入 --skip-image 时只执行容器相关步骤(假定镜像已是最新且已存在)", "yellow")}`,
|
|
176
174
|
` ${helpColor("参数优先级为 CLI > profile > base", "yellow")}`,
|
package/bin/app/parse-args.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cliOptionArgStartIndex = cliOptionArgStartIndex;
|
|
4
|
+
exports.parseProjectTailPositionals = parseProjectTailPositionals;
|
|
3
5
|
exports.parseArgs = parseArgs;
|
|
4
6
|
exports.parseCommandOptions = parseCommandOptions;
|
|
5
7
|
exports.parseCommandPositionals = parseCommandPositionals;
|
|
6
8
|
const KNOWN_COMMANDS = new Set([
|
|
7
9
|
"init",
|
|
8
10
|
"install-skill",
|
|
9
|
-
"
|
|
10
|
-
"release-project",
|
|
11
|
-
"deploy-project",
|
|
11
|
+
"project",
|
|
12
12
|
"lark",
|
|
13
13
|
]);
|
|
14
|
+
function cliOptionArgStartIndex(command) {
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `project <sub> [tag] ...` 中,子命令之后的位置参数(如 release/deploy 的镜像 tag)。
|
|
19
|
+
*/
|
|
20
|
+
function parseProjectTailPositionals(rawArgs) {
|
|
21
|
+
return parseCommandPositionals(rawArgs, "project").slice(1);
|
|
22
|
+
}
|
|
14
23
|
function parseArgs(argv) {
|
|
15
24
|
const rawArgs = [...argv];
|
|
16
25
|
const commandToken = rawArgs[0];
|
|
@@ -41,8 +50,8 @@ function parseArgs(argv) {
|
|
|
41
50
|
positionals,
|
|
42
51
|
};
|
|
43
52
|
}
|
|
44
|
-
function parseCommandOptions(rawArgs) {
|
|
45
|
-
const args = rawArgs.slice(
|
|
53
|
+
function parseCommandOptions(rawArgs, command) {
|
|
54
|
+
const args = rawArgs.slice(cliOptionArgStartIndex(command));
|
|
46
55
|
const profile = readSingleOption(args, "profile");
|
|
47
56
|
const platform = readSingleOption(args, "platform");
|
|
48
57
|
const envEntries = readMultiOption(args, "env");
|
|
@@ -55,8 +64,8 @@ function parseCommandOptions(rawArgs) {
|
|
|
55
64
|
skipImage: skipImage || undefined,
|
|
56
65
|
};
|
|
57
66
|
}
|
|
58
|
-
function parseCommandPositionals(rawArgs) {
|
|
59
|
-
const args = rawArgs.slice(
|
|
67
|
+
function parseCommandPositionals(rawArgs, command) {
|
|
68
|
+
const args = rawArgs.slice(cliOptionArgStartIndex(command));
|
|
60
69
|
const positionals = [];
|
|
61
70
|
const optionsWithValue = new Set(["--profile", "--platform", "--env"]);
|
|
62
71
|
for (let index = 0; index < args.length; index += 1) {
|
package/bin/cli.js
CHANGED
|
@@ -6,9 +6,7 @@ const execute_1 = require("./app/execute");
|
|
|
6
6
|
const command_result_1 = require("./domain/command-result");
|
|
7
7
|
const init_command_1 = require("./commands/init-command");
|
|
8
8
|
const install_skill_command_1 = require("./commands/install-skill-command");
|
|
9
|
-
const
|
|
10
|
-
const release_project_command_1 = require("./commands/release-project-command");
|
|
11
|
-
const deploy_project_command_1 = require("./commands/deploy-project-command");
|
|
9
|
+
const project_command_1 = require("./commands/project-command");
|
|
12
10
|
const lark_command_1 = require("./commands/lark-command");
|
|
13
11
|
const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
|
|
14
12
|
const filesystem_capability_service_1 = require("./services/filesystem-capability-service");
|
|
@@ -33,14 +31,10 @@ const installSkillCommandHandler = (0, install_skill_command_1.createInstallSkil
|
|
|
33
31
|
defaultSkillName: "test",
|
|
34
32
|
},
|
|
35
33
|
});
|
|
36
|
-
const
|
|
37
|
-
fs: fsAdapter,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
fs: fsAdapter,
|
|
41
|
-
});
|
|
42
|
-
const deployProjectCommandHandler = (0, deploy_project_command_1.createDeployProjectCommandHandler)({
|
|
43
|
-
fs: fsAdapter,
|
|
34
|
+
const projectCommandHandler = (0, project_command_1.createProjectCommandHandler)({
|
|
35
|
+
build: { fs: fsAdapter },
|
|
36
|
+
release: { fs: fsAdapter },
|
|
37
|
+
deploy: { fs: fsAdapter },
|
|
44
38
|
});
|
|
45
39
|
const larkCommandHandler = (0, lark_command_1.createLarkCommandHandler)({
|
|
46
40
|
fs: fsAdapter,
|
|
@@ -52,9 +46,7 @@ async function main() {
|
|
|
52
46
|
handlers: {
|
|
53
47
|
init: initCommandHandler,
|
|
54
48
|
"install-skill": installSkillCommandHandler,
|
|
55
|
-
|
|
56
|
-
"release-project": releaseProjectCommandHandler,
|
|
57
|
-
"deploy-project": deployProjectCommandHandler,
|
|
49
|
+
project: projectCommandHandler,
|
|
58
50
|
lark: larkCommandHandler,
|
|
59
51
|
},
|
|
60
52
|
});
|
|
@@ -5,11 +5,11 @@ const parse_args_1 = require("../app/parse-args");
|
|
|
5
5
|
const deploy_project_service_1 = require("../services/deploy-project-service");
|
|
6
6
|
function createDeployProjectCommandHandler(deps) {
|
|
7
7
|
return async (input) => {
|
|
8
|
-
const firstPositional = (0, parse_args_1.
|
|
8
|
+
const firstPositional = (0, parse_args_1.parseProjectTailPositionals)(input.rawArgs)[0];
|
|
9
9
|
const tag = typeof firstPositional === "string" && firstPositional.trim() !== ""
|
|
10
10
|
? firstPositional.trim()
|
|
11
11
|
: undefined;
|
|
12
|
-
const options = (0, parse_args_1.parseCommandOptions)(input.rawArgs);
|
|
12
|
+
const options = (0, parse_args_1.parseCommandOptions)(input.rawArgs, "project");
|
|
13
13
|
const cliOverrides = {
|
|
14
14
|
tag,
|
|
15
15
|
profile: options.profile,
|
|
@@ -133,29 +133,24 @@ async function parseBlocksToMarkdown(blocks, options) {
|
|
|
133
133
|
const token = block.image?.token;
|
|
134
134
|
if (token) {
|
|
135
135
|
const downloadUrl = `https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/v2/cover/${token}/?fallback_source=1&mount_node_token=${block.block_id}&mount_point=docx_image&policy=equal`;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
options.fs.ensureDir(imageDir);
|
|
144
|
-
}
|
|
145
|
-
// 使用 sdk 提供的下载方法,并直接使用 writeFile 写入
|
|
146
|
-
const fileRes = await options.client.drive.media.download({
|
|
147
|
-
path: { file_token: token },
|
|
148
|
-
});
|
|
149
|
-
// 使用官方推荐的 writeFile 方法直接将文件保存到本地
|
|
150
|
-
await fileRes.writeFile(imagePath);
|
|
151
|
-
content += `${indent}\n\n`;
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
console.warn(`[Lark Parse] Download image failed for block ${block.block_id}:`, e);
|
|
155
|
-
content += `${indent}\n\n`;
|
|
136
|
+
try {
|
|
137
|
+
const imageFileName = `assets/${token}.png`; // 保存到 assets 目录
|
|
138
|
+
const imagePath = options.fs.resolve(options.outputDir, imageFileName);
|
|
139
|
+
// 确保 assets 目录存在
|
|
140
|
+
const imageDir = options.fs.dirname(imagePath);
|
|
141
|
+
if (!options.fs.exists(imageDir)) {
|
|
142
|
+
options.fs.ensureDir(imageDir);
|
|
156
143
|
}
|
|
144
|
+
// 使用 sdk 提供的下载方法,并直接使用 writeFile 写入
|
|
145
|
+
const fileRes = await options.client.drive.media.download({
|
|
146
|
+
path: { file_token: token },
|
|
147
|
+
});
|
|
148
|
+
// 使用官方推荐的 writeFile 方法直接将文件保存到本地
|
|
149
|
+
await fileRes.writeFile(imagePath);
|
|
150
|
+
content += `${indent}\n\n`;
|
|
157
151
|
}
|
|
158
|
-
|
|
152
|
+
catch (e) {
|
|
153
|
+
console.warn(`[Lark Parse] Download image failed for block ${block.block_id}:`, e);
|
|
159
154
|
content += `${indent}\n\n`;
|
|
160
155
|
}
|
|
161
156
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProjectCommandHandler = createProjectCommandHandler;
|
|
4
|
+
const command_result_1 = require("../domain/command-result");
|
|
5
|
+
const build_project_command_1 = require("./build-project-command");
|
|
6
|
+
const release_project_command_1 = require("./release-project-command");
|
|
7
|
+
const deploy_project_command_1 = require("./deploy-project-command");
|
|
8
|
+
function createProjectCommandHandler(deps) {
|
|
9
|
+
const build = (0, build_project_command_1.createBuildProjectCommandHandler)(deps.build);
|
|
10
|
+
const release = (0, release_project_command_1.createReleaseProjectCommandHandler)(deps.release);
|
|
11
|
+
const deploy = (0, deploy_project_command_1.createDeployProjectCommandHandler)(deps.deploy);
|
|
12
|
+
return async (input) => {
|
|
13
|
+
const subCommand = input.positionals[0];
|
|
14
|
+
switch (subCommand) {
|
|
15
|
+
case "build":
|
|
16
|
+
return build(input);
|
|
17
|
+
case "release":
|
|
18
|
+
return release(input);
|
|
19
|
+
case "deploy":
|
|
20
|
+
return deploy(input);
|
|
21
|
+
default:
|
|
22
|
+
return (0, command_result_1.commandError)("错误: 未知的子命令。目前仅支持: build, release, deploy", "invalid_subcommand", 1);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -5,11 +5,11 @@ const parse_args_1 = require("../app/parse-args");
|
|
|
5
5
|
const release_project_service_1 = require("../services/release-project-service");
|
|
6
6
|
function createReleaseProjectCommandHandler(deps) {
|
|
7
7
|
return async (input) => {
|
|
8
|
-
const firstPositional = (0, parse_args_1.
|
|
8
|
+
const firstPositional = (0, parse_args_1.parseProjectTailPositionals)(input.rawArgs)[0];
|
|
9
9
|
const tag = typeof firstPositional === "string" && firstPositional.trim() !== ""
|
|
10
10
|
? firstPositional.trim()
|
|
11
11
|
: undefined;
|
|
12
|
-
const options = (0, parse_args_1.parseCommandOptions)(input.rawArgs);
|
|
12
|
+
const options = (0, parse_args_1.parseCommandOptions)(input.rawArgs, "project");
|
|
13
13
|
const cliOverrides = {
|
|
14
14
|
tag,
|
|
15
15
|
profile: options.profile,
|
|
@@ -76,13 +76,36 @@ function getBuildTargets(projectRoot, config) {
|
|
|
76
76
|
projectDir: resolveBuildTarget(projectRoot, item.projectDir),
|
|
77
77
|
}));
|
|
78
78
|
}
|
|
79
|
+
function resolveArtifactsBuildSlice(config) {
|
|
80
|
+
const artifacts = config.artifacts;
|
|
81
|
+
if (artifacts && typeof artifacts === "object" && !Array.isArray(artifacts)) {
|
|
82
|
+
const a = artifacts;
|
|
83
|
+
return {
|
|
84
|
+
frontend: a.frontend ?? config.frontend,
|
|
85
|
+
backend: a.backend ?? config.backend,
|
|
86
|
+
copyFiles: a.copyFiles ?? config.copyFiles,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
frontend: config.frontend,
|
|
91
|
+
backend: config.backend,
|
|
92
|
+
copyFiles: config.copyFiles,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
79
95
|
function toBuildProjectConfig(config) {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
96
|
+
const slice = resolveArtifactsBuildSlice(config);
|
|
97
|
+
const frontendRaw = slice.frontend;
|
|
98
|
+
if (!Array.isArray(frontendRaw) || frontendRaw.length === 0) {
|
|
99
|
+
throw new Error("配置字段无效: frontend (至少包含一个构建项)");
|
|
100
|
+
}
|
|
101
|
+
const backendRaw = slice.backend;
|
|
102
|
+
if (!backendRaw || typeof backendRaw !== "object" || Array.isArray(backendRaw)) {
|
|
103
|
+
throw new Error("配置字段无效: backend");
|
|
104
|
+
}
|
|
82
105
|
return {
|
|
83
106
|
frontend: frontendRaw.map((item) => toBuildTarget(item)),
|
|
84
107
|
backend: toBuildTarget(backendRaw),
|
|
85
|
-
copyFiles: toCopySpecs(
|
|
108
|
+
copyFiles: toCopySpecs(slice.copyFiles),
|
|
86
109
|
};
|
|
87
110
|
}
|
|
88
111
|
function toBuildTarget(item) {
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"react": "^19.2.0",
|
|
14
14
|
"react-dom": "^19.2.0",
|
|
15
|
-
"listpage-next": "~0.0.
|
|
15
|
+
"listpage-next": "~0.0.311",
|
|
16
16
|
"react-router-dom": ">=6.0.0",
|
|
17
17
|
"@ant-design/v5-patch-for-react-19": "~1.0.3",
|
|
18
18
|
"ahooks": "^3.9.5",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"styled-components": "^6.1.19",
|
|
24
24
|
"mobx": "~6.15.0",
|
|
25
25
|
"@ant-design/icons": "~6.0.2",
|
|
26
|
-
"listpage-components": "~0.0.
|
|
26
|
+
"listpage-components": "~0.0.311",
|
|
27
27
|
"lucide-react": "~0.575.0"
|
|
28
28
|
"mobx-react-lite": "~4.1.1"
|
|
29
29
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: listpage-filter-group
|
|
3
|
+
description: 使用 listpage-components 的 FilterGroup 与 FilterFormOption 搭建筛选区。Use ONLY when the user explicitly requests FilterGroup by name (e.g. 使用 FilterGroup、用 FilterGroup 做筛选). Do NOT apply when the user only mentions listpage, ListPage, or listpage pages—those belong to the separate listpage skill; package name listpage-components is unrelated to this trigger. Do not apply for generic filters unless FilterGroup is explicitly named.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FilterGroup 使用技能
|
|
7
|
+
|
|
8
|
+
在用户**明确写出 FilterGroup** 时,按本技能与 [api.md](api.md)、[examples.md](examples.md) 实现。
|
|
9
|
+
|
|
10
|
+
## 命名与触发(与 listpage 页面技能区分)
|
|
11
|
+
|
|
12
|
+
组件从包 `listpage-components` 引入,包名含 **listpage**,易与「用 listpage 做列表页」混淆,但这是**两种独立场景**:
|
|
13
|
+
|
|
14
|
+
| 用户说法 | 使用的技能 |
|
|
15
|
+
|----------|------------|
|
|
16
|
+
| 仅 listpage / ListPage / 用 listpage 实现某页 等 | **listpage 页面技能**,**不要**启用本技能 |
|
|
17
|
+
| 明确写出 **FilterGroup**(或「用 FilterGroup 组件」) | **本技能** |
|
|
18
|
+
|
|
19
|
+
若用户从头到尾只提 listpage、未提 FilterGroup,**忽略本技能**,避免与 listpage 技能抢上下文。
|
|
20
|
+
|
|
21
|
+
## 生成流程
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Task Progress:
|
|
25
|
+
- [ ] 步骤 1:从 listpage-components 导入 FilterGroup、FilterFormOption
|
|
26
|
+
- [ ] 步骤 2:用 FilterFormOption[] 声明字段(name、label、component、colSpan、formItemProps);`component` 可为任意能按注入的 `value`/`onChange`(或 `formItemProps` 中自定义的 valuePropName/trigger)工作的控件,不限组件种类
|
|
27
|
+
- [ ] 步骤 3:按需配置控件细节(placeholder、是否可清空、选项数据等,以用户/产品要求为准)
|
|
28
|
+
- [ ] 步骤 4:实现 onSubmit(参数为筛选条件对象:已去掉值为 `undefined` 的字段,见 api.md);按需实现 onReset
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 在已选用 FilterGroup 时的约定
|
|
32
|
+
|
|
33
|
+
- 避免再手写一套纯 AntD `Form` + 栅格替代本组件;选项形状以 [api.md](api.md) 为准。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# FilterGroup API
|
|
2
|
+
|
|
3
|
+
对应技能仅在用户**明确写出** `FilterGroup` 时启用;用户若只提 listpage / ListPage,**不要**套用本 API 文档(见 SKILL.md 的触发说明)。
|
|
4
|
+
|
|
5
|
+
包名:`listpage-components`(与「listpage 页面技能」无关,仅为 npm 包名)。例如:`import { FilterGroup, type FilterGroupProps, type FilterFormOption } from 'listpage-components'`。
|
|
6
|
+
|
|
7
|
+
## FilterGroup
|
|
8
|
+
|
|
9
|
+
内部组合表单项与栅格布局。
|
|
10
|
+
|
|
11
|
+
### 提交给 `onSubmit` 的数据
|
|
12
|
+
|
|
13
|
+
点击「搜索」或触发与提交等价的逻辑时,`onSubmit` 收到的是**当前筛选条件对象**:在表单取值之后,会**去掉值为 `undefined` 的字段**再传入,便于直接用于请求参数或状态;值为 `null` 的字段会保留,是否参与请求由业务决定。
|
|
14
|
+
|
|
15
|
+
重置流程中在调用 `onReset` 之前,也会用**同样规则**得到当前表单值并调用一次 `onSubmit`(见下)。
|
|
16
|
+
|
|
17
|
+
### FilterGroupProps
|
|
18
|
+
|
|
19
|
+
| 属性 | 类型 | 说明 |
|
|
20
|
+
|------|------|------|
|
|
21
|
+
| `options` | `FilterFormOption[]` | 筛选项配置(必填) |
|
|
22
|
+
| `initialValues` | `FormValue` | 表单初始值 |
|
|
23
|
+
| `onSubmit` | `(values?: FormValue) => void` | 筛选条件变化需提交时触发,参数为上一节说明的对象 |
|
|
24
|
+
| `onReset` | `() => void` | 重置时额外回调(在内部已 `resetFields`、并会再触发一次 `onSubmit` 之后调用) |
|
|
25
|
+
|
|
26
|
+
**重置行为(需牢记)**:用户点「重置」时,会先清空字段,再按上述规则调用 `onSubmit`,最后调用 `onReset`。若你只关心「条件变化后拉数」,通常只实现 `onSubmit` 即可。
|
|
27
|
+
|
|
28
|
+
## FilterFormOption
|
|
29
|
+
|
|
30
|
+
| 字段 | 类型 | 说明 |
|
|
31
|
+
|------|------|------|
|
|
32
|
+
| `name` | `string` | 表单字段名(必填) |
|
|
33
|
+
| `label` | `ReactNode` | 标签文案 |
|
|
34
|
+
| `component` | `ReactElement` | 筛选项控件,**具体用哪种组件不限制**(见下) |
|
|
35
|
+
| `colSpan` | `number` | 栅格占位,对应 12 列网格,**默认 2** |
|
|
36
|
+
| `formItemProps` | 除 `children` 外的 `FormItemProps` | 传给 `Form.Item` 的额外配置;若控件不用 `value`/`onChange`,可在此设 `valuePropName`、`trigger` |
|
|
37
|
+
|
|
38
|
+
### `component` 与受控
|
|
39
|
+
|
|
40
|
+
`FilterItem` 会对 `component` 做 `cloneElement`,注入**当前值**与**变更回调**(默认对应受控的 `value` 与 `onChange`,与 Ant Design Form 常见写法一致)。只要传入的节点能按注入后的 props 作为受控组件工作即可,**不限定**必须是 `Input`、`Select`、`DatePicker` 等某一种。
|
|
41
|
+
|
|
42
|
+
若控件使用其它字段名(例如 `checked`),通过 `formItemProps.valuePropName`、`formItemProps.trigger` 与 Ant Design `Form.Item` 约定一致即可。
|
|
43
|
+
|
|
44
|
+
未提供 `component` 时,该筛选项使用默认的 `Input`。
|
|
45
|
+
|
|
46
|
+
布局使用内部 `FilterGridLayout`(`grid-cols-12`),操作按钮占一列;筛选项 `colSpan` 为 1–12。
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# FilterGroup 示例
|
|
2
|
+
|
|
3
|
+
本技能仅在用户**明确写出 FilterGroup** 时启用;以下为实现参考。
|
|
4
|
+
|
|
5
|
+
## 仅筛选区 + 本地状态或请求
|
|
6
|
+
|
|
7
|
+
示例里使用 `Input` / `Select` / `DatePicker` 仅为演示;`component` 可换成任意合适的受控控件(见 [api.md](api.md) 中 `component` 说明)。示例中部分使用 `allowClear` 仅为常见写法,以实际需求为准。
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { useState, useCallback } from 'react';
|
|
11
|
+
import { Input, Select, DatePicker } from 'antd';
|
|
12
|
+
import type { Dayjs } from 'dayjs';
|
|
13
|
+
import {
|
|
14
|
+
FilterGroup,
|
|
15
|
+
type FilterFormOption,
|
|
16
|
+
} from 'listpage-components';
|
|
17
|
+
|
|
18
|
+
type FilterValues = {
|
|
19
|
+
keyword?: string;
|
|
20
|
+
status?: string;
|
|
21
|
+
date?: Dayjs;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function ReportFilterPanel() {
|
|
25
|
+
const [filters, setFilters] = useState<FilterValues>({});
|
|
26
|
+
|
|
27
|
+
const options: FilterFormOption[] = [
|
|
28
|
+
{
|
|
29
|
+
name: 'keyword',
|
|
30
|
+
label: '关键词',
|
|
31
|
+
colSpan: 3,
|
|
32
|
+
component: <Input placeholder="请输入" allowClear />,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'status',
|
|
36
|
+
label: '状态',
|
|
37
|
+
colSpan: 2,
|
|
38
|
+
component: (
|
|
39
|
+
<Select
|
|
40
|
+
allowClear
|
|
41
|
+
placeholder="全部"
|
|
42
|
+
options={[
|
|
43
|
+
{ label: '启用', value: '1' },
|
|
44
|
+
{ label: '停用', value: '0' },
|
|
45
|
+
]}
|
|
46
|
+
/>
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'date',
|
|
51
|
+
label: '日期',
|
|
52
|
+
colSpan: 3,
|
|
53
|
+
component: <DatePicker className="w-full" allowClear />,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const handleSubmit = useCallback((values?: FilterValues) => {
|
|
58
|
+
setFilters(values ?? {});
|
|
59
|
+
// 此处可改为调用 API:fetchReport({ ...values })
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="bg-white px-6 py-4 rounded-lg border border-[#f0f0f0]">
|
|
64
|
+
<FilterGroup<FilterValues>
|
|
65
|
+
options={options}
|
|
66
|
+
initialValues={filters}
|
|
67
|
+
onSubmit={handleSubmit}
|
|
68
|
+
onReset={() => {
|
|
69
|
+
/* 可选:仅重置以外的副作用 */
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 自检
|
|
78
|
+
|
|
79
|
+
- 各控件的交互(是否可清空、选项来源等)与用户/产品要求一致
|
|
80
|
+
- `name` 与类型/接口字段一致
|
|
81
|
+
- 理解「重置」会再次触发 `onSubmit`,避免重复请求未做防抖时可在业务侧处理
|
|
82
|
+
|