rocket-list-cli 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 King
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # rocket-list-cli
2
+
3
+ 根据 Admin 后台配置,一键生成列表页面(含 API、列表组件、搜索栏、表格等)。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g rocket-list-cli
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ```bash
14
+ rocket gen <configId> [appPath]
15
+ ```
16
+
17
+ | 参数 | 说明 | 默认值 |
18
+ |------|------|--------|
19
+ | `configId` | Admin 配置 ID(必填) | - |
20
+ | `appPath` | 输出目录 | `apps/react/src` |
21
+
22
+ ### 选项
23
+
24
+ | 选项 | 说明 | 默认值 |
25
+ |------|------|--------|
26
+ | `--api-url` | Admin API 地址 | `http://localhost:3033/api/v1/admin/detail` |
27
+ | `--templates` | 模板目录(相对项目根目录) | 内置模板 |
28
+ | `-v, --version` | 显示版本号 | - |
29
+ | `-h, --help` | 显示帮助信息 | - |
30
+
31
+ ### 示例
32
+
33
+ ```bash
34
+ # 使用配置 1 生成页面到默认目录
35
+ rocket gen 1
36
+
37
+ # 指定输出目录
38
+ rocket gen 1 apps/react/src
39
+
40
+ # 自定义 API 地址
41
+ rocket gen 2 apps/vue/src --api-url http://localhost:4000/api/v1/admin/detail
42
+
43
+ # 使用自定义模板
44
+ rocket gen 1 src --templates my-templates
45
+ ```
46
+
47
+ ## 生成的文件结构
48
+
49
+ ```
50
+ appPath/
51
+ ├── {apiName}.ts # API 请求文件
52
+ └── {name}/
53
+ ├── index.tsx # 页面入口
54
+ ├── columns/
55
+ │ ├── index.ts # 列定义
56
+ │ ├── RenderActions.tsx # 操作列渲染
57
+ │ └── RenderGender.tsx # 性别列渲染
58
+ └── modules/
59
+ ├── SearchBar.tsx # 搜索栏组件
60
+ └── PageTable.tsx # 表格组件
61
+ ```
62
+
63
+ ## 编程方式调用
64
+
65
+ ```typescript
66
+ import { ListPageGenerator } from 'rocket-list-cli';
67
+
68
+ const generator = new ListPageGenerator({
69
+ workspaceRoot: '/path/to/project',
70
+ adminApiUrl: 'http://localhost:3033/api/v1/admin/detail',
71
+ });
72
+
73
+ const result = await generator.generate('1', 'apps/react/src');
74
+ ```
75
+
76
+ ## 环境要求
77
+
78
+ - Node.js >= 18
79
+ - 项目根目录需存在 `turbo.json`
80
+
81
+ ## License
82
+
83
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { ListPageGenerator, findWorkspaceRoot } from './generator.js';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
9
+ const HELP = `
10
+ rocket gen <configId> [appPath]
11
+
12
+ configId Admin config ID (required)
13
+ appPath Output directory, defaults to apps/react/src
14
+
15
+ Options:
16
+ --api-url Admin API URL, defaults to http://localhost:3033/api/v1/admin/detail
17
+ --templates Templates directory (relative to project root), defaults to turbo/generators/templates
18
+ -v, --version Show version
19
+ -h, --help Show help
20
+
21
+ Examples:
22
+ rocket gen 1
23
+ rocket gen 1 apps/react/src
24
+ rocket gen 2 apps/vue/src --api-url http://localhost:4000/api/v1/admin/detail
25
+ rocket gen 1 src --templates my-templates
26
+ `.trim();
27
+ function parseArgs(argv) {
28
+ const args = argv.slice(2);
29
+ const flags = {};
30
+ const positional = [];
31
+ for (let i = 0; i < args.length; i++) {
32
+ const arg = args[i];
33
+ if (arg === '-h' || arg === '--help') {
34
+ console.log(HELP);
35
+ process.exit(0);
36
+ }
37
+ if (arg === '-v' || arg === '--version') {
38
+ console.log(pkg.version);
39
+ process.exit(0);
40
+ }
41
+ if (arg === '--api-url' && args[i + 1]) {
42
+ flags.apiUrl = args[++i];
43
+ }
44
+ else if (arg === '--templates' && args[i + 1]) {
45
+ flags.templates = args[++i];
46
+ }
47
+ else if (arg.startsWith('--')) {
48
+ // skip unknown flags
49
+ }
50
+ else {
51
+ positional.push(arg);
52
+ }
53
+ }
54
+ return { positional, flags };
55
+ }
56
+ async function main() {
57
+ const { positional, flags } = parseArgs(process.argv);
58
+ const command = positional[0];
59
+ if (!command || command === 'help') {
60
+ console.log(HELP);
61
+ process.exit(command ? 0 : 1);
62
+ }
63
+ if (command !== 'gen') {
64
+ console.error(`Unknown command: ${command}`);
65
+ console.log(`\n${HELP}`);
66
+ process.exit(1);
67
+ }
68
+ const configId = positional[1];
69
+ if (!configId) {
70
+ console.error('Error: missing configId argument');
71
+ console.log(`\n${HELP}`);
72
+ process.exit(1);
73
+ }
74
+ const appPath = positional[2] ?? 'apps/react/src';
75
+ const workspaceRoot = findWorkspaceRoot(process.cwd());
76
+ if (!workspaceRoot) {
77
+ console.error('Error: turbo.json not found. Run this command inside a project directory.');
78
+ process.exit(1);
79
+ }
80
+ const templatesDir = flags.templates
81
+ ? join(workspaceRoot, flags.templates)
82
+ : undefined;
83
+ const generator = new ListPageGenerator({
84
+ workspaceRoot,
85
+ templatesDir,
86
+ adminApiUrl: flags.apiUrl,
87
+ });
88
+ console.log(`\nConfig ID : ${configId}`);
89
+ console.log(`Output : ${appPath}`);
90
+ console.log(`Root : ${workspaceRoot}\n`);
91
+ const result = await generator.generate(configId, appPath);
92
+ if (!result.success) {
93
+ console.error(`\u2717 ${result.message}: ${result.error}`);
94
+ process.exit(1);
95
+ }
96
+ console.log(`\u2713 ${result.message}\n`);
97
+ console.log(` Page : ${result.details.name}`);
98
+ console.log(` Route : ${result.details.routerPath}`);
99
+ console.log(` Menu : ${result.details.menuName}\n`);
100
+ console.log('Generated files:');
101
+ for (const file of result.details.generatedFiles) {
102
+ console.log(` + ${file}`);
103
+ }
104
+ console.log();
105
+ }
106
+ main().catch((err) => {
107
+ console.error('Fatal error:', err.message);
108
+ process.exit(1);
109
+ });
@@ -0,0 +1,47 @@
1
+ export declare function toKebabCase(str: string): string;
2
+ export declare function toPascalCase(str: string): string;
3
+ export interface AdminConfig {
4
+ id: number;
5
+ name: string;
6
+ path: string;
7
+ routerPath?: string;
8
+ componentPath?: string;
9
+ apiPath?: string;
10
+ icon: string;
11
+ api: string;
12
+ type: string;
13
+ schema: string;
14
+ }
15
+ export interface GenerateResult {
16
+ success: boolean;
17
+ message: string;
18
+ details?: {
19
+ configId: string;
20
+ appPath: string;
21
+ name: string;
22
+ apiName: string;
23
+ routerPath: string;
24
+ menuName: string;
25
+ icon: string;
26
+ generatedFiles: string[];
27
+ };
28
+ error?: string;
29
+ }
30
+ export interface GeneratorOptions {
31
+ workspaceRoot: string;
32
+ templatesDir?: string;
33
+ adminApiUrl?: string;
34
+ }
35
+ export declare class ListPageGenerator {
36
+ private workspaceRoot;
37
+ private templatesDir;
38
+ private adminApiUrl;
39
+ constructor(options: GeneratorOptions);
40
+ fetchAdminConfig(id: string): Promise<AdminConfig>;
41
+ private renderTemplate;
42
+ generate(id: string, appPath: string): Promise<GenerateResult>;
43
+ }
44
+ /**
45
+ * Walk up from startDir looking for turbo.json to locate the workspace root.
46
+ */
47
+ export declare function findWorkspaceRoot(startDir: string): string | null;
@@ -0,0 +1,129 @@
1
+ import Handlebars from 'handlebars';
2
+ import { join, dirname } from 'path';
3
+ import { readFile, mkdir, writeFile } from 'fs/promises';
4
+ import { existsSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const BUILTIN_TEMPLATES_DIR = join(__dirname, '../templates');
9
+ export function toKebabCase(str) {
10
+ return str
11
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
12
+ .replace(/[\s_]+/g, '-')
13
+ .toLowerCase();
14
+ }
15
+ export function toPascalCase(str) {
16
+ return str
17
+ .split(/[-_\s]+/)
18
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
19
+ .join('');
20
+ }
21
+ Handlebars.registerHelper('pascalCase', (str) => toPascalCase(str));
22
+ Handlebars.registerHelper('kebabCase', (str) => toKebabCase(str));
23
+ const TEMPLATE_FILES = [
24
+ { template: 'api.hbs', getPath: (_name, apiName) => `${apiName}.ts` },
25
+ { template: 'list/index.hbs', getPath: (name) => `${name}/index.tsx` },
26
+ { template: 'list/columns/index.hbs', getPath: (name) => `${name}/columns/index.ts` },
27
+ {
28
+ template: 'list/columns/RenderActions.hbs',
29
+ getPath: (name) => `${name}/columns/RenderActions.tsx`,
30
+ },
31
+ {
32
+ template: 'list/columns/RenderGender.hbs',
33
+ getPath: (name) => `${name}/columns/RenderGender.tsx`,
34
+ },
35
+ {
36
+ template: 'list/modules/SearchBar.hbs',
37
+ getPath: (name) => `${name}/modules/SearchBar.tsx`,
38
+ },
39
+ {
40
+ template: 'list/modules/PageTable.hbs',
41
+ getPath: (name) => `${name}/modules/PageTable.tsx`,
42
+ },
43
+ ];
44
+ export class ListPageGenerator {
45
+ workspaceRoot;
46
+ templatesDir;
47
+ adminApiUrl;
48
+ constructor(options) {
49
+ this.workspaceRoot = options.workspaceRoot;
50
+ this.templatesDir = options.templatesDir ?? BUILTIN_TEMPLATES_DIR;
51
+ this.adminApiUrl = options.adminApiUrl ?? 'http://localhost:3033/api/v1/admin/detail';
52
+ }
53
+ async fetchAdminConfig(id) {
54
+ const response = await fetch(this.adminApiUrl, {
55
+ method: 'POST',
56
+ headers: { 'Content-Type': 'application/json' },
57
+ body: JSON.stringify({ id: Number(id) }),
58
+ });
59
+ if (!response.ok) {
60
+ throw new Error(`Admin API request failed: ${response.status}`);
61
+ }
62
+ const result = await response.json();
63
+ if (result.code !== 0) {
64
+ throw new Error(result.msg || 'Failed to fetch config');
65
+ }
66
+ return result.data;
67
+ }
68
+ async renderTemplate(templateName, data) {
69
+ const templatePath = join(this.templatesDir, templateName);
70
+ const source = await readFile(templatePath, 'utf-8');
71
+ return Handlebars.compile(source)(data);
72
+ }
73
+ async generate(id, appPath) {
74
+ try {
75
+ const config = await this.fetchAdminConfig(id);
76
+ const rawName = config.componentPath || config.path.replace(/^\//, '');
77
+ const name = toKebabCase(rawName);
78
+ const apiName = toKebabCase(config.apiPath || '');
79
+ const routerPath = config.routerPath || config.path;
80
+ const templateData = { name, apiName, schemaData: config.schema, adminConfig: config };
81
+ const basePath = join(this.workspaceRoot, appPath);
82
+ const generatedFiles = [];
83
+ for (const { template, getPath } of TEMPLATE_FILES) {
84
+ const relPath = getPath(name, apiName);
85
+ const fullPath = join(basePath, relPath);
86
+ await mkdir(dirname(fullPath), { recursive: true });
87
+ const content = await this.renderTemplate(template, templateData);
88
+ await writeFile(fullPath, content, 'utf-8');
89
+ generatedFiles.push(join(appPath, relPath));
90
+ }
91
+ return {
92
+ success: true,
93
+ message: 'Page generated successfully',
94
+ details: {
95
+ configId: id,
96
+ appPath,
97
+ name,
98
+ apiName,
99
+ routerPath,
100
+ menuName: config.name,
101
+ icon: config.icon,
102
+ generatedFiles,
103
+ },
104
+ };
105
+ }
106
+ catch (error) {
107
+ return {
108
+ success: false,
109
+ message: 'Page generation failed',
110
+ error: error.message,
111
+ };
112
+ }
113
+ }
114
+ }
115
+ /**
116
+ * Walk up from startDir looking for turbo.json to locate the workspace root.
117
+ */
118
+ export function findWorkspaceRoot(startDir) {
119
+ let dir = startDir;
120
+ while (true) {
121
+ if (existsSync(join(dir, 'turbo.json'))) {
122
+ return dir;
123
+ }
124
+ const parent = dirname(dir);
125
+ if (parent === dir)
126
+ return null;
127
+ dir = parent;
128
+ }
129
+ }
@@ -0,0 +1,2 @@
1
+ export { ListPageGenerator, findWorkspaceRoot, toKebabCase, toPascalCase, } from './generator.js';
2
+ export type { AdminConfig, GenerateResult, GeneratorOptions, } from './generator.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { ListPageGenerator, findWorkspaceRoot, toKebabCase, toPascalCase, } from './generator.js';
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "rocket-list-cli",
3
+ "version": "0.1.0",
4
+ "description": "Super List page generator CLI",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "King",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/king/super-list",
11
+ "directory": "packages/cli"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "bin": {
22
+ "rocket": "dist/cli.js"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "templates",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "dev": "tsx watch src/cli.ts",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "keywords": [
35
+ "cli",
36
+ "generator",
37
+ "list",
38
+ "page",
39
+ "rocket-list"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "dependencies": {
45
+ "handlebars": "^4.7.8"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.10.1",
49
+ "tsx": "^4.19.2",
50
+ "typescript": "^5.7.2"
51
+ }
52
+ }
@@ -0,0 +1,41 @@
1
+ import request from "../utils/request";
2
+
3
+ const api = {
4
+ list(data: any) {
5
+ return request({
6
+ url: "/api/v1/{{apiName}}/list",
7
+ method: "post",
8
+ data,
9
+ });
10
+ },
11
+ info(data: any) {
12
+ return request({
13
+ url: "/api/v1/{{apiName}}/info",
14
+ method: "post",
15
+ data,
16
+ });
17
+ },
18
+ create(data: any) {
19
+ return request({
20
+ url: "/api/v1/{{apiName}}/create",
21
+ method: "post",
22
+ data,
23
+ });
24
+ },
25
+ update(data: any) {
26
+ return request({
27
+ url: "/api/v1/{{apiName}}/update",
28
+ method: "post",
29
+ data,
30
+ });
31
+ },
32
+ delete(data: any) {
33
+ return request({
34
+ url: "/api/v1/{{apiName}}/delete",
35
+ method: "post",
36
+ data,
37
+ });
38
+ },
39
+ };
40
+
41
+ export default api;
@@ -0,0 +1,19 @@
1
+ import api from "../../api/{{apiName}}";
2
+ import SearchBar from "./modules/search-bar";
3
+ import useList from "../../core/hooks/useList";
4
+ import PageList from "./modules/page-list";
5
+
6
+ const initialValues = { current: 1, pageSize: 10 }
7
+
8
+ const {{name}}Page = () => {
9
+ const listStore = useList({ api, initialValues });
10
+
11
+ return (
12
+ <div>
13
+ <SearchBar {...listStore} />
14
+ <PageList {...listStore} />
15
+ </div>
16
+ );
17
+ };
18
+
19
+ export default {{name}}Page;
@@ -0,0 +1,19 @@
1
+ import api from "../../api/{{apiName}}";
2
+ import SearchBar from "./modules/search-bar";
3
+ import useList from "../../core/hooks/useList";
4
+ import PageList from "./modules/page-list";
5
+
6
+ const initialValues = { current: 1, pageSize: 10 }
7
+
8
+ const {{name}}Page = () => {
9
+ const listStore = useList({ api, initialValues });
10
+
11
+ return (
12
+ <div>
13
+ <SearchBar {...listStore} />
14
+ <PageList {...listStore} />
15
+ </div>
16
+ );
17
+ };
18
+
19
+ export default {{name}}Page;
@@ -0,0 +1,15 @@
1
+ import { Button } from 'antd';
2
+
3
+ const RenderActions = (props: any) => {
4
+ const { row, onDelete } = props;
5
+
6
+ return (
7
+ <div>
8
+ <Button>查看</Button>
9
+ <Button>编辑</Button>
10
+ <Button onClick={() => onDelete({ id: row.id })}>删除</Button>
11
+ </div>
12
+ );
13
+ };
14
+
15
+ export default RenderActions;
@@ -0,0 +1,13 @@
1
+ import { Tag } from 'antd';
2
+
3
+ const genderRender = (props: any) => {
4
+ const { row = {} } = props;
5
+ if (row.gender === 1) {
6
+ return <Tag color="blue">男</Tag>;
7
+ } else if (row.gender === 2) {
8
+ return <Tag color="pink">女</Tag>;
9
+ }
10
+ return <Tag color="gray">未知</Tag>;
11
+ };
12
+
13
+ export default genderRender;
@@ -0,0 +1,22 @@
1
+ import { createElement } from 'react';
2
+ import RenderActions from './RenderActions';
3
+ import RenderGender from './RenderGender';
4
+
5
+ const getColumns = (props: any) => {
6
+ const { onDelete } = props;
7
+
8
+ {{#if schemaData}}
9
+ return {{{schemaData}}}
10
+ {{else}}
11
+ return [
12
+ {
13
+ key: 'action',
14
+ title: '操作',
15
+ dataIndex: 'action',
16
+ render: (_, row) => createElement(RenderActions, { row, onDelete }, null),
17
+ },
18
+ ]
19
+ {{/if}}
20
+ };
21
+
22
+ export default getColumns;
@@ -0,0 +1,25 @@
1
+ import { useEffect } from 'react';
2
+ import api from '@/api/{{apiName}}';
3
+ import useList from '@/core/useList';
4
+ import SearchBar from './modules/SearchBar';
5
+ import PageTable from './modules/PageTable';
6
+
7
+ const {{pascalCase name}} = () => {
8
+ const { data, loading, onLoad, onSearch, onReset, onDelete } = useList({
9
+ api,
10
+ defaultParams: { current: 1, pageSize: 10 },
11
+ });
12
+
13
+ useEffect(() => {
14
+ onLoad({});
15
+ }, []);
16
+
17
+ return (
18
+ <div>
19
+ <SearchBar onSearch={onSearch} onReset={onReset} onCreate={() => {}} />
20
+ <PageTable data={data} onSearch={onSearch} onDelete={onDelete} loading={loading} />
21
+ </div>
22
+ );
23
+ };
24
+
25
+ export default {{pascalCase name}};
@@ -0,0 +1,22 @@
1
+ import TablePro from '@/components/TablePro';
2
+ import getColumns from '../columns';
3
+
4
+ const PageList = (props: any) => {
5
+ const { data, onSearch, pageInfo, onDelete, loading } = props;
6
+ const columns = getColumns({ onDelete });
7
+
8
+ return (
9
+ <div className="bg-white p-4 rounded-lg shadow-sm">
10
+ <TablePro
11
+ pageInfo={pageInfo}
12
+ columns={columns}
13
+ list={data?.list || []}
14
+ total={data?.total || 0}
15
+ loading={loading}
16
+ onChange={onSearch}
17
+ />
18
+ </div>
19
+ );
20
+ };
21
+
22
+ export default PageList;
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import { Button, Form, Select, Input } from 'antd';
3
+
4
+ interface ISearchBar {
5
+ onSearch: (values: any) => Promise<void>;
6
+ onReset: () => Promise<void>;
7
+ onCreate: () => void;
8
+ }
9
+
10
+ const SearchBar: React.FC<ISearchBar> = (props) => {
11
+ const { onSearch, onReset, onCreate } = props;
12
+
13
+ const [form] = Form.useForm();
14
+
15
+ const onFinish = (values: any) => {
16
+ onSearch(values);
17
+ };
18
+
19
+ const onResetHandler = () => {
20
+ form.resetFields();
21
+ onReset();
22
+ };
23
+ const onGenderClear = () => {
24
+ form.setFieldsValue({ gender: undefined });
25
+ form.submit();
26
+ };
27
+
28
+ return (
29
+ <div className="bg-white p-4 mb-4 rounded-lg shadow">
30
+ <Form
31
+ form={form}
32
+ layout="inline"
33
+ onFinish={onFinish}
34
+ >
35
+ <Form.Item label="姓名" name="name">
36
+ <Input placeholder="请输入姓名" />
37
+ </Form.Item>
38
+
39
+ <Form.Item wrapperCol=\{{ offset: 8, span: 16 }}>
40
+ <Button type="primary" htmlType="submit">
41
+ 搜索
42
+ </Button>
43
+ </Form.Item>
44
+ <Form.Item wrapperCol=\{{ offset: 8, span: 16 }}>
45
+ <Button onClick={onResetHandler}>重置</Button>
46
+ </Form.Item>
47
+ <Form.Item wrapperCol=\{{ offset: 8, span: 16 }}>
48
+ <Button type="primary" onClick={onCreate}>
49
+ 创建
50
+ </Button>
51
+ </Form.Item>
52
+ </Form>
53
+ </div>
54
+ );
55
+ };
56
+
57
+ export default SearchBar;