node实现自动生成vue页面,更新router

背景:最近负责将页面迁移到新系统,每次在新系统新增页面都需要做一系列重复操作:

  1. views下新建页面文件夹及index页面
  2. 更新router.js
  3. api文件夹下新增对应页面的js文件

管理系统的页面大差不差,基本围绕增删改查,加上系统有些需要注意的点,于是整理了个模板文件,减少工作量。

本来vscode也可以设置模板文件的,但是由于2和3,为了简化操作,使用node写了个脚本一次性完成

系统页面结构

如果不符合此页面结构的需要自行修改方法

  • src
    • xx(新页面)
      • component
        • add-dia.vue(新增功能封装的弹窗)
      • index.vue(主页面)
  • 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发送日志

相关推荐
lzp079117 小时前
如何在Windows系统上安装和配置Node.js及Node版本管理器(nvm)
windows·node.js
weiwx8319 小时前
【前端】Node.js使用教程
前端·node.js·vim
i建模20 小时前
Ubuntu Node.js 升级方案
linux·运维·ubuntu·node.js
结网的兔子1 天前
前端学习笔记(实战准备篇)——用vite构建一个项目【吐血整理】
前端·学习·elementui·npm·node.js·vue
i建模1 天前
npm国内镜像源加速
前端·npm·node.js
热爱生活的五柒2 天前
解决 npm install 一直在转圈的问题
前端·npm·node.js
跟着珅聪学java2 天前
Electron 中实现“字符串转图片”功能教程
node.js
tryCbest2 天前
Git与Node.js安装及常用命令详解
git·node.js
_DoubleL2 天前
Volta启动项目自动切换Node版本
前端·node.js
小杨勇敢飞2 天前
npm 安装 @openai/codex 后无法调用 codex 命令的完整解决过程:‘codex‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。
前端·npm·node.js