Vuex 自动化生成工具
Vuex 自动化生成工具需求文档
1. 需求背景
为提升前端开发效率,减少重复代码编写,需开发一个自动化工具,根据输入参数自动生成完整的 Vuex 存储模块(包括api.js,mutations.js,actions.js,getters.js,mutation-types。js)。
2. 功能需求
2.1 输入参数
参数名 | 类型 | 必填 | 描述 | 示例值 |
---|---|---|---|---|
apiName | string | 是 | API 名称(驼峰命名) | getUserInfo |
apiUrl | string | 是 | API 地址 | /api/user/info |
remark | string | 否 | 接口注释 | 获取用户详细信息 |
requestType | string | 否 | HTTP 方法(默认 GET) | post |
apiType | string | 否 | API 分类 | get create update download |
2.2 生成文件及规则
2.2.1 api.js 文件
- 功能:存储 API 地址常量
- 生成规则:
/*** 获取用户详细信息*/ export const getUserInfo = '/api/user/info';
- 校验:
- 文件不存在时自动创建
- 接口名称或地址重复时抛出错误
2.2.2 mutation-types.js 文件
- 功能:定义 mutation 类型常量
- 生成规则:
/*** 获取用户详细信息*/ export const GETUSERINFO_REQUEST = 'GETUSERINFO_REQUEST'; export const GETUSERINFO_RECEIVE = 'GETUSERINFO_RECEIVE';
2.2.3 mutations.js 文件
- 功能:生成状态变更逻辑
- 生成规则:
[type.GETUSERINFO_REQUEST](state) {state.userInfoStatus = 'loading';state.userInfo = {}; }, [type.GETUSERINFO_RECEIVE](state, res) {state.userInfoStatus = 'loaded';state.userInfo = res?.content || {}; }
2.2.4 getters.js
- 功能:生成状态获取方法
- 生成规则:
export const userInfo = state => state.userInfo || {}; export const userInfoStatus = state => state.userInfoStatus || '';
2.2.5 actions.js
- 功能:生成 API 调用逻辑
- 生成规则:
export const getUserInfo = ({ commit }, data) => {commit(type.GETUSERINFO_REQUEST, data);return ajax.post(API.getUserInfo,{ commit },data,type.GETUSERINFO_RECEIVE); };
3. 非功能性需求
3.1 错误处理
- 接口名称/地址重复时中止操作并提示
- 文件权限不足时抛出异常
3.2 目录管理
- 默认生成到
./store/modules/
目录 - 自动创建缺失的目录层级
3.3 代码风格
- 统一使用 2 空格缩进
- 自动生成 JSDoc 注释
4. 使用示例
// 调用示例
addStoreToFile('getUser', '/api/user', '获取用户数据', 'get', 'user');
5. 输出验证
执行成功后需生成以下文件结构:
store/modules/api.jsmutations.jsgetters.jsactions.jsmutation-types.js
基础代码
const fs = require('fs');
const path = require('path');
const BASE_DIR = './store/user'; // 所有文件生成到store/modules下
const apiFilePath = `${BASE_DIR}/api.js`
const mutationsFilePath = `${BASE_DIR}/mutations.js`
const actionsFilePath = `${BASE_DIR}/actions.js`
const mutationTypesFilePath = `${BASE_DIR}/mutation-types.js`
const gettersFilePath = `${BASE_DIR}/getters.js`
// 校验文件内是否已经存在
function _checkCorrect(filePath, regStr, apiUrl) {// 1. 检查文件是否存在if (!fs.existsSync(filePath)) {fs.writeFileSync(filePath, '', 'utf8');}// 2. 读取文件内容const content = fs.readFileSync(filePath, 'utf8');// 3. 检查接口是否已存在const apiRegex = new RegExp(regStr, 'g');if (regStr && apiRegex.test(content)) {throw new Error(`已存在`);return false}// 4. 检查URL是否已存在if (apiUrl && content.includes(apiUrl)) {throw new Error(`接口地址 ${apiUrl} 已存在`);return false}return true
}
// 匹配是否满足条件的函数
function _checkFunctionExists(content, apiName) {// 匹配两种函数定义方式:// 1. export const fnName = ({ commit }) => {...}// 2. export function fnName({ commit }) {...}const funcRegex = new RegExp(`export\\s+(?:const\\s+${apiName}\\s*=\\\s*\\(|function\\s+${apiName}\\s*\\\()`);return funcRegex.test(content);
}
// 重新设置名称
function _getNewApiName(apiName, requestType, apiType) {if (apiType) {// 将apiName首字母大写apiName = apiName.replace(apiName[0], apiName[0].toUpperCase())apiName = requestType == 'get' ? (requestType + apiName) : (apiType + apiName)return apiName}return apiName
}
// 在文件顶部添加创建目录的函数
function _ensureDirectoryExists() {if (!fs.existsSync(BASE_DIR)) {fs.mkdirSync(BASE_DIR, { recursive: true });}
}
// 1. api写入
function addApiToFile(apiName, apiUrl, remark = '', requestType, apiType) {let filePath = `${BASE_DIR}/api.js`let isCorrect = _checkCorrect(apiFilePath, `export const ${apiName} *= *['"].+['"]`, apiUrl)if (isCorrect) {const newApi = `
/*** ${remark} */
export const ${apiName} = '${apiUrl}';\n`;fs.appendFileSync(apiFilePath, newApi, 'utf8');return `接口 ${apiName} 添加成功`;}}
// 2. mutation-types写入
function addMutationTypes(apiName, remark) {apiName = apiName.toUpperCase()let isCorrect1 = _checkCorrect(mutationTypesFilePath, `export const ${apiName + '_REQUEST'} *= *['"].+['"]`)let isCorrect2 = _checkCorrect(mutationTypesFilePath, `export const ${apiName + '_RECEIVE'} *= *['"].+['"]`)if (isCorrect1 && isCorrect2) {let newApi = `
/*** ${remark} */
export const ${apiName + '_REQUEST'} = '${apiName + '_REQUEST'}';\n
export const ${apiName + '_RECEIVE'} = '${apiName + '_RECEIVE'}';\n`;fs.appendFileSync(mutationTypesFilePath, newApi, 'utf8');return `mutation-types ${apiName} 添加成功`;}
}
//3. getters 写入
function addGettersToFile(apiName, remark) {let isCorrect1 = _checkCorrect(gettersFilePath, `export const ${apiName + 'Info'} *= *['"].+['"]`)let isCorrect2 = _checkCorrect(gettersFilePath, `export const ${apiName + 'Status'} *= *['"].+['"]`)if (isCorrect1 && isCorrect2) {let newApi = `
/*** ${remark} */
export const ${apiName + 'Info'} = state =>state[${apiName + 'Info'}]||{};\n
export const ${apiName + 'Status'} = state =>state[${apiName + 'Status'}]||'';\n`;fs.appendFileSync(gettersFilePath, newApi, 'utf8');return `getters ${apiName} 添加成功`;}
}
// 4. mutations 写入
function addMutationsToFile(apiName, remark) {// 1. 初始化文件(如果需要)if (!fs.existsSync(mutationsFilePath)) {fs.writeFileSync(mutationsFilePath, `
import * as type from './mutation-types'
export const state = {}
export const mutations = {}`.trim(), 'utf8');}// 2. 读取最新内容(每次重新读取)let content = fs.readFileSync(mutationsFilePath, 'utf8');// 3. 处理stateconst stateRegex = /export\s+const\s+state\s*=\s*{([^{}]*?(?:\{[^{}]*\}[^{}]*?)*)}/ ///export\s+const\s+state\s*=//\s*{([\s\S]*?)}/;if (stateRegex.test(content)) {content = content.replace(stateRegex, (match, stateContent) => {if (new RegExp(`${apiName}Info\\s*:|${apiName}Status\\s*:`).test(stateContent)) {throw new Error(`State中已存在${apiName}相关属性`);}return `export const state = {${stateContent.trim()}${remark ? `// ${remark}` : ''}${apiName}Info: {},${apiName}Status: '',
}`;});}// 4. 处理mutations(关键修正)const mutationRegex = /export\s+const\s+mutations\s*=\s*{([\s\S]*?)}\s*$/;if (mutationRegex.test(content)) {content = content.replace(mutationRegex, (match, mutationContent) => {if (new RegExp(`\\[type\\.${apiName.toUpperCase()}_(REQUEST|RECEIVE)\\]`).test(mutationContent)) {throw new Error(`Mutations中已存在${apiName}相关操作`);}return `export const mutations = {${mutationContent.trim()}${remark ? `// ${remark}` : ''}[type.${apiName.toUpperCase()}_REQUEST](state) {state.${apiName}Status = 'loading';state.${apiName}Info = {};},[type.${apiName.toUpperCase()}_RECEIVE](state, res) {state.${apiName}Status = 'loaded';state.${apiName}Info = res?.content || {};},
}`;});}// 5. 写入最终结果fs.writeFileSync(mutationsFilePath, content, 'utf8');return `${apiName}相关mutations配置已添加`;
}
//5. actions.js
function addActionsToFile(apiName, remark, requestType) {// 1. 初始化文件(如果需要)if (!fs.existsSync(actionsFilePath)) {fs.writeFileSync(actionsFilePath, `
import * as type from './mutation-types'
import * as API from './api'
import { ajax } from '@/utils/fetch'`.trim(), 'utf8');}// 2. 读取最新内容(每次重新读取)let content = fs.readFileSync(actionsFilePath, 'utf8');// 3. 判断文件中是否存在函数名称为apiName的函数if (_checkFunctionExists(content, apiName)) {throw new Error(`函数 ${apiName} 已存在`);}// 4. 追加新函数const newAction = `
${remark ? `// ${remark}` : ''} export const ${apiName} = ({ commit }, data) => {commit(type.${apiName.toUpperCase()}_REQUEST, data);return ajax.${requestType}(API.${apiName},{ commit },data,type.${apiName.toUpperCase()}_RECEIVE);};`;fs.appendFileSync(actionsFilePath, newAction, 'utf8');return `${apiName}相关actions配置已添加`;}// apiType:['get', 'create', 'download', 'update', 'delete']
function addStoreToFile(apiName, apiUrl, remark = '', requestType = 'get', apiType) {_ensureDirectoryExists()let newApiName = _getNewApiName(apiName, apiType)// 1. 添加 APIaddApiToFile(newApiName, apiUrl, remark,)// 2. 添加 addMutationTypesaddMutationTypes(apiName, remark)// 3. 添加 gettersaddGettersToFile(apiName, remark)// 4. 添加 mutationsaddMutationsToFile(apiName, remark)// 5. 添加 actionsaddActionsToFile(newApiName, remark, requestType)return 'success'}try {// console.log(addStoreToFile('getName', 'api/user/name', '获取名称'));// console.log(addStoreToFile('getUser', 'api/user/User', '获取用户信息'));console.log(addStoreToFile('getStore1', 'api/user/store1', '获取用户信息', 'post'));} catch (error) {console.error(error.message);
}