1、vsCode代码片段
①vsCode中搜索"代码片段"
②重新新建一个代码片段文件
③命名文件名称
④打开snippet-generator.app/
⑤放代码片段里边
⑥输入指令"react-components"生成代码片段
2、nodeCli脚本快速生成一个列表和表单
在开发后台管理系统时,我们遇到了一个典型的场景:项目包含上百个功能相似的页面,基本都是基于增删改查的基础架构,只有业务逻辑存在差异。目前的解决方案是通过复制现有页面代码再手动修改,但这种方式存在几个明显问题:
- 需要频繁复制粘贴相似代码
- 必须手动清理无关的业务逻辑代码
- 每个模块涉及多个文件(列表、表单、路由配置等)
- 修改维护成本高,容易产生冗余代码
更理想的解决方案是开发一个Node.js命令行工具,通过自动化生成来提升效率。具体实现思路如下:
- 基于模板引擎动态生成标准化的页面代码
- 自动创建关联文件(列表组件、表单组件等)
- 智能更新路由配置文件
- 支持通过一条命令快速生成完整的功能模块
这个方案的核心在于文件系统的读写操作,通过程序化方式将模板代码写入指定位置。相比手动复制,自动化生成具有以下优势:
- 确保代码一致性
- 减少人为错误
- 提升开发效率
- 便于统一维护
接下来,我们将具体实现这个Node.js命令行工具,通过模板化和自动化来解决批量页面开发的问题。
①我们先确定一个思路,定义两个方法,一个生成代码文件,一个往路由文件里边加代码
先在项目根目录新增一个scripts/generateModule.js文件
const createModuleFiles = ()=>{
console.log('新增了模块')
}
const updateRoutes = ()=>{
console.log("往路由里加路径")
}
// 执行
createModuleFiles();
updateRoutes();
②在package.json下增加一条命令
"generate": "node scripts/generateModule.js",
③在控制台上执行"pnpm generate ModuleName"
④现在先判断文件夹中是否已存在我们要创建的文件,没有则创建文件
const fs = require('fs');
const path = require('path');
// 获取命令行参数(模块名称)
const moduleName = process.argv[2];//pnpm generate Test(即拿到Test)
if (!moduleName) {
console.error('请提供模块名称,例如:pnpm generate Test');
process.exit(1);
}
// 路径定义
const srcDir = path.join(__dirname, '..', 'src');
const pageDir = path.join(srcDir, 'pages');
const moduleDir = path.join(pageDir, moduleName);
const componentsDir = path.join(moduleDir, 'components');
const createModuleFiles = () => {
try {
// 创建page目录
if (!fs.existsSync(pageDir)) {
fs.mkdirSync(pageDir, { recursive: true });
}
// 创建模块目录
if (!fs.existsSync(moduleDir)) {
fs.mkdirSync(moduleDir, { recursive: true });
}
// 创建components目录
if (!fs.existsSync(componentsDir)) {
fs.mkdirSync(componentsDir, { recursive: true });
}
} catch {
console.error('创建目录失败');
}
}
const updateRoutes = () => {
}
// 执行
createModuleFiles();
updateRoutes();
⑤我们需要生成两个文件,一个是列表文件,一个是表单文件,我们得分别定义这两个文件的代码模板
const fs = require('fs');
const path = require('path');
// 获取命令行参数(模块名称)
const moduleName = process.argv[2];//pnpm generate Test(即拿到Test)
if (!moduleName) {
console.error('请提供模块名称,例如:pnpm generate Test');
process.exit(1);
}
const ListTemplate = `
import React, { memo, ReactNode } from 'react';
interface IProps {
children?: ReactNode;
}
/**
* react组件
* @returns
*/
const name: React.FC<IProps> = () => {
return <div className="name">name</div>;
};
export default memo(name);
`
const formTemplate = `
import React, { memo, ReactNode } from 'react';
interface IProps {
children?: ReactNode;
}
/**
* 表单组件名称
* @returns
*/
const EditForm: React.FC<IProps> = () => {
return <div className="editForm">EditForm</div>;
};
export default memo(EditForm);
`
// 路径定义
const srcDir = path.join(__dirname, '..', 'src');
const pageDir = path.join(srcDir, 'pages');
const moduleDir = path.join(pageDir, moduleName);
const componentsDir = path.join(moduleDir, 'components');
const createModuleFiles = () => {
try {
// 创建page目录
if (!fs.existsSync(pageDir)) {
fs.mkdirSync(pageDir, { recursive: true });
}
// 创建模块目录
if (!fs.existsSync(moduleDir)) {
fs.mkdirSync(moduleDir, { recursive: true });
}
// 创建components目录
if (!fs.existsSync(componentsDir)) {
fs.mkdirSync(componentsDir, { recursive: true });
}
// 写入文件
fs.writeFileSync(path.join(moduleDir, 'index.tsx'), ListTemplate);
fs.writeFileSync(
path.join(componentsDir, 'EditForm.tsx'),
formTemplate,
);
} catch {
console.error('创建目录失败');
}
}
const updateRoutes = () => {
}
// 执行
createModuleFiles();
updateRoutes();
到这一步,我们可以执行命令生成文件了,我们来看看效果:
⑥现在我们在路由中自动引入的代码,在updateRoutes方法中实现
const routesFile = path.join(srcDir, 'routes', 'index.ts');
// 模块名称(首字母大写,用于组件名)
const capitalizedModuleName =
moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
const formattedModuleName = moduleName
.replace(/([A-Z])/g, ' $1') // 在大写字母前添加空格
.trim() // 移除开头可能的空格
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
const camelModuleName = moduleName
.replace(/([A-Z])/g, (match, p1, offset) => {
return offset === 0 ? p1.toLowerCase() : '-' + p1.toLowerCase();
})
.split('-')
.filter((word) => word) // 过滤空字符串
.join('-');
const routeEntriesTemplate = ` {
path: '/${camelModuleName}',
component: './${capitalizedModuleName}',
name: '${formattedModuleName}',
},
{
path: '/new-${camelModuleName}/:id',
component: './${capitalizedModuleName}/components/EditForm',
name: '${formattedModuleName}',
hideInMenu: true,
},
{
path: '/update-${camelModuleName}/:id',
component: './${capitalizedModuleName}/components/EditForm',
name: '${formattedModuleName}',
hideInMenu: true,
},
{
path: '/view-${camelModuleName}/:id',
component: './${capitalizedModuleName}/components/EditForm',
name: '${formattedModuleName}',
hideInMenu: true,
},
`;
const updateRoutes = () => {
try {
if (!fs.existsSync(routesFile)) {
console.error('routes/index.ts 文件不存在!');
process.exit(1);
}
// 读取routes文件内容
let routesContent = fs.readFileSync(routesFile, 'utf-8');
// 查找路由数组的结束位置
const routesArrayEnd = routesContent.lastIndexOf('];');
if (routesArrayEnd === -1) {
console.error('routes/index.ts 文件格式不正确,未找到路由数组!');
process.exit(1);
}
// 检查是否已存在任一目标路由
const targetPaths = [
`path: '/${moduleName}'`,
`path: '/new-${moduleName}/:id'`,
`path: '/update-${moduleName}/:id'`,
`path: '/view-${moduleName}/:id'`,
];
if (targetPaths.some((path) => routesContent.includes(path))) {
console.log(`模块 ${moduleName} 的路由已存在,跳过路由更新。`);
return;
}
// 检查最后一个路由条目前是否已有逗号
const contentBeforeEnd = routesContent.slice(0, routesArrayEnd).trimEnd();
const needsComma = contentBeforeEnd && !contentBeforeEnd.endsWith(',');
// 在路由数组结束前插入新路由
routesContent = `${contentBeforeEnd}${
needsComma ? ',' : ''
}\n${routeEntriesTemplate}${routesContent.slice(routesArrayEnd)}`;
// 写入更新后的routes文件
fs.writeFileSync(routesFile, routesContent);
console.log('routes/index.ts 文件更新成功!');
} catch (err) {
console.error('更新 routes/index.ts 失败:', err);
process.exit(1);
}
}
最后,我们来看一下效果:
更新引用成功