背景:最近负责将页面迁移到新系统,每次在新系统新增页面都需要做一系列重复操作:
- 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结构
...
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
}
},
]
}
}
具体实现
创建文件夹、读写文件
// 写入文件
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}`);
}
};
模板变量替换
// 变量替换
const replaceStr = (str, config) => {
str = str.replace(/%selfMoudle%/g, config.selfMoudle); // 页面名
return str;
};
模板页面变量示范:
创建index.vue
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
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
先找到要新增页面对应的目录,再写入值
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;
// 查找 routes 属性
const routesProp = routerValue.properties.find((prop) => prop.key.name === "routes");
if (!routesProp || !t.isArrayExpression(routesProp.value)) return;
// 递归查找并更新路由
const findAndUpdate = (elements) => {
for (const element of elements) {
if (!t.isObjectExpression(element)) continue;
// 查找 name 属性
const nameProp = element.properties.find(
(prop) => t.isObjectProperty(prop) && prop.key.name === "name" && prop.value.value === config.parentMoudle
);
if (nameProp) {
// 查找或创建 children 属性
const childrenProp = element.properties.find(
(prop) => t.isObjectProperty(prop) && prop.key.name === "children"
);
if (childrenProp && t.isArrayExpression(childrenProp.value)) {
// 如果 children 属性存在,直接 push 新路由
childrenProp.value.elements.push(setAst(config));
} else {
// 如果 children 属性不存在,创建 children 属性并添加新路由
element.properties.push(
t.objectProperty(t.identifier("children"), t.arrayExpression([setAst(config)]))
);
}
return; // 找到并更新后退出
}
// 如果有 children 属性,递归查找
const childrenProp = element.properties.find(
(prop) => t.isObjectProperty(prop) && prop.key.name === "children"
);
if (childrenProp && t.isArrayExpression(childrenProp.value)) {
findAndUpdate(childrenProp.value.elements);
}
}
};
// 开始查找并更新
findAndUpdate(routesProp.value.elements);
}
},
});
// 生成新的代码
const newCode = generator(ast, {}, fileContent).code;
// 写入文件
await writeF(filePath, newCode);
};
主方法
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发送日志