阅读视图

发现新文章,点击刷新页面。

Vue 开发者的外挂工具:配置一个 JSON,自动造出一整套页面!

🧰 引言:你是不是也这样?

作为一名前端开发者,你有没有经历过下面这些场景?

“又来一个新模块?好,先去 copy 个模板,改改名字……再写点基本结构……”

“这个组件结构每次都差不多,能不能别让我手敲了?”

“团队里每个人写的 Vue 文件格式都不一样,review 起来头大……”

如果你点头了,那这篇博客就是为你准备的!

今天我要分享的是我自己开发的一个 CLI 工具 —— catalog_file_generator,它能让你:

  • ✅ 通过一个配置文件,一键生成 Vue 页面和组件;
  • ✅ 支持多种模板类型(Vue2、Vue3、script setup);
  • ✅ 自动创建多层级目录结构;
  • ✅ 统一项目风格,提升团队协作效率;

一句话总结:它是一个“造房子”的工具,你只管画设计图,它帮你盖楼。


🔨 它到底能干啥?

场景一:批量创建模块?一行命令搞定!

你想创建 abcd 这几个模块,每个模块下都有 index.vueedit.vue,甚至还有子目录 components

传统做法是:新建目录 ➜ 拷贝模板 ➜ 改名 ➜ 修改内容 ➜ 循环重复……

catalog_file_generator 的话,只需要一个配置文件:

json
深色版本
{
  "a": {
    "index.vue": { "content": "a列表", "template": "v2" },
    "edit.vue": { "content": "a编辑", "template": "v2" }
  },
  "b": {
    "index.vue": { "content": "b列表", "template": "v2" },
    "edit.vue": { "content": "b编辑", "template": "v2" }
  },
  "c": {
    "index.vue": { "content": "c列表", "template": "v2" },
    "edit.vue": { "content": "c编辑", "template": "v2" },
    "info.vue": { "content": "c详情", "template": "v2" }
  },
  "d": {
    "components": {
      "tool.vue": { "content": "d工具组件", "template": "v2" }
    },
    "index.vue": { "content": "d列表", "template": "v2" }
  }
}

然后执行命令:

cf-cli generate -c config.json -o src/views

Boom!目录结构瞬间就建好了!


场景二:统一代码风格?模板说了算!

团队协作中,最怕的就是风格不统一。有人喜欢用 <script setup>,有人偏爱 Vue2 的 Options API。

怎么办?用 catalog_file_generator,直接在配置里指定模板路径:

json
深色版本
{
  "user": {
    "index.vue": {
      "content": "用户列表",
      "template": "/templates/vue3sTemp.vue"
    }
  }
}

所有生成的文件都使用同一个模板,风格一致,review 不头疼。


场景三:快速搭建 MVP?几分钟搞定几十个页面!

创业、内部孵化、临时需求……时间紧任务重?

用这个工具,几分钟就能搭出几十个页面结构,把精力留给真正重要的功能逻辑。


🚀 怎么安装和使用?

安装方式:

npm install -g catalog_file_generator

安装完成后,输入:

cf-cli --help

你会看到如下命令:

Usage: cf-cli [options] [command]

Commands:
  module     交互式生成模块(支持不同文件选择不同模板 + 输入中文内容)
  generate   根据配置文件生成模块结构(支持 .js/.json)

Options:
  -V, --version  输出版本号
  -h, --help     显示帮助信息

🛠️ 命令详解

1. 交互式创建模块:cf-cli module

适用于临时新增模块,比如用户管理、订单页等。

示例命令:

cf-cli module -n user,order --files index,edit,detail -o src/views

参数说明:

参数 含义
-n--name 模块名,多个用逗号分隔
--files 要生成的文件名列表,默认是 index
-o--output 输出目录,默认是 ./dist

执行后会进入交互流程:

  1. 选择模板类型:Vue2 / Vue3 / script setup / 自定义路径;
  2. 输入中文描述:自动替换模板中的占位符(如 #name#content);

2. 配置文件生成结构:cf-cli generate

适合一次性批量生成多个模块。

示例命令:

cf-cli generate -c config.json -o src/modules

参数说明:

参数 含义
-c--config 配置文件路径(支持 .json 或 .js
-o--output 输出目录,默认是 ./dist

📦 支持的模板类型一览

类型 示例 说明
内置模板 "v2" 使用工具自带的 Vue2 模板
绝对路径 "/templates/vue3sTemp.vue" 相对于项目根目录查找
相对路径 "../custom-templates/form.vue" 相对于当前模块目录查找

🧪 小试牛刀:试试看!

示例输出结构:

运行完命令后,会在 src/modules/ 下生成如下结构:

深色版本
src/modules/
├── user/
│   ├── index.vue
│   ├── edit.vue
│   └── detail.vue
└── order/
    ├── index.vue
    ├── edit.vue
    └── detail.vue

每个 .vue 文件都会根据你选择的模板和内容自动填充内容,无需手动编写。


⚙️ 如何封装到自己的项目脚本中?

你可以封装一个 Node.js 脚本来调用这个 CLI,方便集成到你的项目中。

创建 scripts/cf-page.js

#!/usr/bin/env node

const { spawn } = require('child_process');
const chalk = require('chalk');
const path = require('path');
const [name] = process.argv.slice(2);

if (!name) {
  console.error(chalk.red('❌ 请提供模块名称,例如:npm run cf:page UserPage'));
  process.exit(1);
}

const cliEntry = path.resolve(__dirname, '../node_modules/catalog_file_generator/cli.js');

const args = [
  'module',
  '-n', name,
  '--files', 'index',
  '-o', 'src/components'
];

const child = spawn('node', [cliEntry, ...args], { stdio: 'inherit' });

child.on('error', (err) => {
  console.error(chalk.red(`❌ 子进程启动失败:${err.message}`));
  process.exit(1);
});

child.on('close', (code) => {
  if (code === 0) {
    console.log(chalk.green(`✅ 模块【${name}】创建成功!`));
  } else {
    console.error(chalk.red(`❌ 创建失败,退出码:${code}`));
  }
});

在 package.json 中添加脚本:

"scripts": {
  "cf:page": "node scripts/cf-page.js"
}

使用方式:

npm run cf:page UserPage

🌟 总结:为什么你应该试试它?

功能 亮点
🧩 支持交互式创建模块 每个文件都能选模板、填内容
📄 支持配置文件驱动 一次生成多个模块,结构清晰可复用
🎨 多种模板类型可选 支持 Vue2/Vue3,也可自定义路径
📂 支持嵌套结构生成 灵活控制目录层级
🌈 带颜色的日志提示 提升用户体验,便于排查问题

🎉 结语:别让工具牵着你走,要让它为你服务!

catalog_file_generator 不只是一个脚手架工具,它更像是一位“代码建筑师”,你只需要告诉它你要什么结构,剩下的交给它就行。

下次当你又要手写第 10 个 Vue 文件的时候,不妨试试这个工具,让你从重复劳动中解放出来,去做更有价值的事!


📌 GitHub 仓库链接?

👉 我已经打包发布到了 npm,你可以直接使用 npm install -g catalog_file_generator 安装。

📌 想扩展功能?

👉 欢迎 fork、PR、提 issue,我们一起打造更强大的前端代码生成器!


🎯 最后送大家一句话:

“程序员的价值不是写多少行代码,而是让代码尽可能少地写。”

用工具解放双手,才是真正的“高效编程”。


觉得有帮助的话,记得点个赞、收藏、转发哦~
💬 欢迎留言交流你日常开发中遇到的重复性工作,我们可以一起想办法自动化解决!

在生产环境下,你真的有考虑到使用数组方法的健壮性吗?

场景

在一个风和日丽的清晨,刚进公司我就看到测试小哥眉头紧锁,疯狂的在工位上面摆弄自己的鼠标。突然抬头看到我进来,急急忙忙的说:火锅哥,快过来搂一眼!正式环境上突然图表的数据没了。昨天还好好的,今天早上领导突然要看效果,把链接一打开进入数据大屏就不行了。

看着测试要背锅的样子,只能帮忙排查一下问题呗! 如果是没有数据,那咱就先抓包看一下~

但是看到网络请求里面,后端所有数据都是正常返回的,为什么没渲染呢? 难道是前端代码报错导致打断了渲染?

咱从网络又切换到了控制台~ 好家伙果然是报错了。

image.png

然后马上打开了vscode,找到了这行代码的逻辑,如下:

image.png

原因就是res是后端接口的返回,按正常情况应该返回一个Array数组,结果实际返回了一个"null",那自然而然就JS报错了呗!

于是乎找到后端大佬说明了情况,谁知大佬这天来了大姨妈,心情及其暴躁~ 吼着说: 逻辑不会永远按正常情况返回,你前端不做代码健壮性处理?而且即使我返回的是null,与下面渲染逻辑有什么关系吗? 你该渲染还是渲染啊!

听到大佬这么回答,顿时我与测试都陷入了沉默。(JS的报错确实会打断后续逻辑的交付,大佬这么讲也没什么毛病)

测试:火锅哥,你改一下吧! 不然老板怪我没测出来,帮你点杯蜜雪呗!

我:行吧行吧~ 反正加一行判断的事~

image.png

改完准备发版的时候,后端大佬又来了一句,不只这里可能会返回null,其他的接口你也注意看一下哦!

听到大佬的这话,我彷佛天都要塌~ 那我的这个工作量太大了!每个都要去加判断呀,由于当时项目急着上线,很多这种接口的地方,都没做容错处理。

想到以后所有的项目都是找大佬对接接口,如果每次写代码都这样写,或者有稍微一不注意漏掉的地方,下次测试就直接给我提BUG了,这点逼绩效全部都要扣完。

于是乎我决定造一个轮子工具,针对处理数组的轮子工具,即使类型不是数组,也不能出现报错打断的情况。让逻辑继续走,顶多报一个警告提示出来,后续再处理!

safe-array-utils诞生,源码如下:


class SafeArrayWrapper {
  #array;
  constructor(input, error) {
    let array = [];
    const rawType = Object.prototype.toString.call(input);
    //首先做类型的判断:
    //1.如果是数组就不管,直接赋值
    //2.如果是类数组,就直接转换成数组
    //3.如果是其他类型,就直接抛警告 
    //4.最后不是数组类型的数据,全部赋值为空数组
    if (Array.isArray(input)) {
      array = input;
    } else if (
      rawType === '[object Arguments]' ||
      (typeof input === 'object' && input !== null && 'length' in input)
    ) {
      try {
        array = Array.from(input);
      } catch (e) {
        console.warn(`[SafeArray] 类数组转换失败`);
      }
    } else {
      console.warn(`[SafeArray] 输入不是合法数组或类数组。类型:${rawType},定位标记:${error || '无'}`);
    }

    this.#array = array;
  }
  

  //下面就是针对数组原型上面一些方法的操作了,保留原数组原型方法的特性

  static chainableMethods = ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap', 'reverse', 'sort'];
  static terminalMethods = [
    'join', 'reduce', 'find', 'findIndex', 'includes',
    'indexOf', 'lastIndexOf', 'every', 'some', 'at',
    'toString', 'toLocaleString'
  ];

  static allowedMethods = [...this.chainableMethods, ...this.terminalMethods, 'value'];

  static #proxyMethod(methodName) {
    return function (...args) {
      const target = this.#array;

      if (typeof target[methodName] !== 'function') {
        console.error(`数组不存在名为 "${methodName}" 的方法`);
        return this;
      }

      const result = Array.prototype[methodName].apply(target, args);

      if (SafeArrayWrapper.chainableMethods.includes(methodName)) {
        return new SafeArrayWrapper(result).#array;
      } else {
        return result;
      }
    };
  }

  value() {
    return [...this.#array];
  }

  static {
    for (const method of this.allowedMethods) {
      this.prototype[method] = this.#proxyMethod(method);
    }
  }
}

function SafeArray(input, error) {
  return new SafeArrayWrapper(input, error);
}

export default SafeArray;

其实整个轮子的核心逻辑就是,类型是数组就还是走数组方法的操作,类型不是数组就抛出警告,同时把传入的值赋值为空数组。

同时为了使用方便,我把它打成了一个npm包,包名就叫:safe-array-utils

1.正常用法:

 import SafeArray from 'safe-array-utils'
 let arr = [1,2,3,4]
 
 //与使用数组方法一样,只是把数组包裹一层函数调用
 SafeArray(arr).forEach(el=>{
    console.log(el)
 })
 let newArr = SafeArray(arr).map(el=>{
    return el * 2
 })
 
 //同时也支持链式调用
   let arr2 = [1,2,3,4,undefined]
  let newArr = SafeArray(arr2).filter(_=>_).map(el=>{
    return el * 2
 })
 
 //包括其它的数组方法,如
 let arr2 = [1,2,3,4]
 SafeArray(arr2).at(1)
 SafeArray(arr2).join('-')
 

2.如果传入错误类型


//即使传的类型不对,也不会报错了。更不会打断下面的逻辑
 let newArr = SafeArray('null').map(el=>{
    return el * 2
 })
 console.log(newArr) // 空数组 []
 console.log('正常打印')
 

image.png

3.假如页面多处地方用到

    //可以传第二个参数,加一个标记定位
    SafeArray('null','第一处').map(el => {
      return el * 2
    })
    SafeArray('null','第二处').map(el => {
      return el * 2
    })
    SafeArray('null','第三处').map(el => {
      return el * 2
    })
  

image.png

怎么样,兄弟们~ 没有什么事是不能用造1个轮子解决的。如果有,那就造2个!

总结:

还是回到最初的问题,即使后端没按照数组的方式返回结果,我们也能正常走后续的逻辑,从而不会因为页面报错,影响页面上的其他功能。最后我想请问家人们你们平时写代码会注重代码的健壮性吗? 我想如果是项目工期短,时间紧。会很少有人关注这一块,甚至现在还有很多小公司不会去做单元测试。如果该文章对你有帮助,就请点赞+收藏吧!如果你们项目中也遇到这种情况。你会使用哪种方式去做呢?欢迎留言讨论~

❌