背景:最近负责将页面迁移到新系统,每次在新系统新增页面都需要做一系列重复操作:
- views下新建页面文件夹及index页面
- 更新router.js
- api文件夹下新增对应页面的js文件
管理系统的页面大差不差,基本围绕增删改查,加上系统有些需要注意的点,于是整理了个模板文件,减少工作量。
本来vscode也可以设置模板文件的,但是由于2和3,为了简化操作,使用node写了个脚本一次性完成
系统页面结构
如果不符合此页面结构的需要自行修改方法

- src
- xx(新页面)
- component
- add-dia.vue(新增功能封装的弹窗)
- index.vue(主页面)
- component
- xx(新页面)
- api
- xx.js(新页面对应的api)
- setting
- router.js
router.js结构
js
...
const router = {
mode: "hash",
routerHook: router => {
router.beforeEach((to, from, next) => {
next();
});
},
routes: [
{
path: "/xxx",
name: "yyy",
meta: {
title: "zzz",
constant: true,
icon: "menu:jccs"
},
children: [
{
path: "/www",
name: "www",
component: () => import("views/mmm/www/index.vue"),
meta: {
title: "ppp",
constant: true
}
},
]
}
}
具体实现
创建文件夹、读写文件
js
// 写入文件
const writeF = async (path, content) => {
try {
await fs.writeFile(path, content);
console.log(`${path} 写入成功`);
} catch (err) {
console.error(`${path} 写入失败: ${err.message}`);
}
};
// 读取文件
const readF = async (path) => {
try {
const data = await fs.readFile(path, "utf8");
return data;
} catch (err) {
console.error(`${path} 读取失败: ${err.message}`);
return null;
}
};
// 创建文件夹并写入文件
const mkdirWithWriteF = async (path, needCom, fn) => {
try {
await fs.mkdir(path + (needCom ? compPath : ""), { recursive: true });
console.log(`${path} 创建成功`);
fn && fn();
} catch (err) {
console.error(`${path} 创建失败: ${err.message}`);
}
};
模板变量替换
js
// 变量替换
const replaceStr = (str, config) => {
str = str.replace(/%selfMoudle%/g, config.selfMoudle); // 页面名
return str;
};
模板页面变量示范:

创建index.vue
js
const writePageTemp = async (paths, config) => {
const templatePath = config.needEmpty ? emptyTempPath : defaultTempPath;
const templateContent = await readF(templatePath);
if (templateContent) {
const str = replaceStr(templateContent, config);
await writeF(paths + defaultVuePath, str);
}
};
创建api.js
js
const writeApiTemp = async (config) => {
const paths = path.resolve(root + `/src/api/${config.selfMoudle}.js`);
// if (!examFileExist(paths)) return;
const apiContent = await readF(apiTempPath);
if (apiContent) {
await writeF(paths, apiContent);
}
};
修改router.js
先找到要新增页面对应的目录,再写入值
只支持一级目录下新增路由,如果有二级目录需要自行修改
js
const setAst = (config) => {
return t.objectExpression([
t.objectProperty(t.identifier("path"), t.stringLiteral("/" + config.selfMoudle)),
t.objectProperty(t.identifier("name"), t.stringLiteral(config.selfMoudle)),
t.objectProperty(
t.identifier("component"),
t.arrowFunctionExpression(
[], // 无参数
t.callExpression(t.import(), [t.stringLiteral(`views/${config.parentDir}/${config.selfMoudle}/index.vue`)])
)
),
t.objectProperty(
t.identifier("meta"),
t.objectExpression([
t.objectProperty(t.identifier("title"), {
type: "StringLiteral",
value: config.title,
extra: { raw: `"${config.title}"`, rawValue: config.title }, // 手动设置 raw 值
}),
t.objectProperty(t.identifier("constant"), t.booleanLiteral(true)),
])
),
]);
};
const updateRouterTemp = async (config) => {
const filePath = path.resolve(root + `/src/setting/router.js`);
console.log("updateRouterTemp", filePath);
const fileContent = await readF(filePath);
if (!fileContent) return;
// 解析文件内容为 AST
const ast = parser.parse(fileContent, {
sourceType: "module",
});
// 遍历 AST,找到 router 变量
traverse(ast, {
VariableDeclarator(path) {
if (path.node.id.name === "router") {
const routerValue = path.node.init;
const curRes = routerValue.properties.find((prop) => prop.key.name == "routes");
curRes.value.elements.forEach((element) => {
if (
t.isObjectExpression(element) &&
element.properties.some(
(prop) => t.isObjectProperty(prop) && prop.key.name === "name" && prop.value.value === config.parentMoudle
)
) {
const childrenProp = element.properties.find(
(prop) => t.isObjectProperty(prop) && prop.key.name === "children"
);
if (childrenProp && t.isArrayExpression(childrenProp.value)) {
childrenProp.value.elements.push(setAst(config));
} else {
console.log("这个模块不存在children属性,检查是否出错");
}
}
});
}
},
});
// 生成新的代码
const newCode = generator(ast, {}, fileContent).code;
// 写入文件
await writeF(filePath, newCode);
};
主方法
js
const addTemplate = async (params) => {
console.log("传递的参数", params);
if (params.length < 4) {
console.log("传递母模块名,母文件夹名,自身模块名,页面名,是否需要空页面模板");
return;
}
// 读取传递的变量
const config = {
parentMoudle: params[0],
parentDir: params[1],
// 驼峰改横线命名
selfMoudle: params[2].replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
title: params[3],
needEmpty: params[4] || false,
};
console.log("config", config);
const paths = path.resolve(root + `/src/views/${config.parentDir}/${config.selfMoudle}`);
console.log("paths is:" + paths);
// TODO: existsSync方法报错不存在,等解决后会更新
// if (!examFileExist(paths)) {
// console.log("不存在同名父模块文件夹,现在新建一个");
// await mkdirWithWriteF(paths, false);
// }
console.log("开始生成模板页面");
console.log("要添加的文件夹路径:" + paths);
await mkdirWithWriteF(paths, true, async () => {
const addDiaContent = await readF(addDiaTempPath); // 添加add-dia
if (addDiaContent) {
const str = replaceStr(addDiaContent, config);
await writeF(path.join(paths, compPath, addDiaPath), str);
}
await writePageTemp(paths, config); // 添加index.vue
});
await writeApiTemp(config); // 添加api.js
await updateRouterTemp(config); // 更新router.js
console.log("生成结束,检查一下");
};
addTemplate(process.argv.slice(2));
运行
在package.json中配置命令

运行示例
将脚本文件夹放在项目中,运行: npm run cpage basic-params base-data agent-api-log API发送日志