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 +21 -0
- package/README.md +83 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +109 -0
- package/dist/generator.d.ts +47 -0
- package/dist/generator.js +129 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +52 -0
- package/templates/api.hbs +41 -0
- package/templates/form/index.component.hbs +19 -0
- package/templates/info/index.component.hbs +19 -0
- package/templates/list/columns/RenderActions.hbs +15 -0
- package/templates/list/columns/RenderGender.hbs +13 -0
- package/templates/list/columns/index.hbs +22 -0
- package/templates/list/index.hbs +25 -0
- package/templates/list/modules/PageTable.hbs +22 -0
- package/templates/list/modules/SearchBar.hbs +57 -0
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
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
|
+
}
|
package/dist/index.d.ts
ADDED
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;
|