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);
}