Skip to content

Vue——vue3 之 代码生成器原理

Vue | July 11, 2026


文章链接:https://blog.csdn.net/qq_43201350/article/details/157541112

背景问题: 需要理解代码生成器的实现原理。

方案思考: 实现一个简单的代码生成器。

具体实现: 代码生成器:

// utils/code-generator.js
// 代码生成器类
export class CodeGenerator {
  constructor(options = {}) {
    this.options = {
      templateDir: options.templateDir || './templates',
      outputDir: options.outputDir || './output',
      ...options
    }
  }
  
  // 生成Vue组件
  static generateVueComponent(config) {
    const { name, props, template, script, style } = config
    
    return `
<template>
  <div class="${toKebabCase(name)}">
    ${template || ''}
  </div>
</template>

<script setup>
${script || 'import { ref } from \'vue\''}

${props ? `defineProps({\n  ${props.map(prop => `${prop.name}: ${prop.type}`).join(',\n  ')}\n})` : ''}
</script>

<style scoped>
${style || `.${toKebabCase(name)} {\n  /* 样式 */\n}`}
</style>`
  }
  
  // 生成API文件
  static generateApiFile(config) {
    const { moduleName, baseUrl, methods } = config
    
    const apiMethods = methods.map(method => {
      const { name, url, method: httpMethod, params } = method
      return `export function ${name}(${params ? 'params' : ''}) {
  return request({
    url: '${url}',
    method: '${httpMethod.toLowerCase()}',
    ${params ? `${httpMethod.toLowerCase() === 'get' ? 'params' : 'data'}: params` : ''}
  })
}`
    }).join('\n\n')
    
    return `import request from '@/utils/request'

${apiMethods}`
  }
  
  // 生成Store
  static generateStore(config) {
    const { name, state, actions, getters } = config
    
    return `import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const use${capitalize(name)}Store = defineStore('${name}', () => {
  // State
  ${Object.entries(state).map(([key, value]) => 
    `const ${key} = ref(${JSON.stringify(value)})`
  ).join('\n  ')}

  // Getters
  ${getters ? Object.entries(getters).map(([key, fn]) => 
    `const ${key} = computed(() => ${fn})`
  ).join('\n  ') : ''}

  // Actions
  ${actions ? Object.entries(actions).map(([key, fn]) => 
    `const ${key} = ${fn.toString()}`
  ).join('\n  ') : ''}

  return {
    ${[...Object.keys(state), ...Object.keys(getters || {}), ...Object.keys(actions || {})].join(',\n    ')}
  }
})`
  }
  
  // 生成路由
  static generateRoute(config) {
    const { path, name, component, meta } = config
    
    return `{
  path: '${path}',
  name: '${name}',
  component: () => import('@/views/${component}.vue'),
  meta: ${JSON.stringify(meta, null, 2)}
}`
  }
}

// 辅助函数
function toKebabCase(str) {
  return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
}

function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

代码生成工具:

// utils/generator-tools.js
import { CodeGenerator } from './code-generator'

// 项目生成器
export class ProjectGenerator {
  // 生成CRUD页面
  static generateCRUD(config) {
    const { moduleName, fields, hasPagination = true } = config
    
    // 生成列表页面
    const listPage = this.generateListPage(config)
    
    // 生成表单页面
    const formPage = this.generateFormPage(config)
    
    // 生成API
    const apiFile = CodeGenerator.generateApiFile({
      moduleName,
      baseUrl: `/api/${moduleName}`,
      methods: [
        { name: `${moduleName}List`, url: `/${moduleName}/list`, method: 'GET', params: true },
        { name: `get${capitalize(moduleName)}Info`, url: `/${moduleName}/info`, method: 'GET', params: true },
        { name: `create${capitalize(moduleName)}`, url: `/${moduleName}`, method: 'POST', params: true },
        { name: `update${capitalize(moduleName)}`, url: `/${moduleName}`, method: 'PUT', params: true },
        { name: `delete${capitalize(moduleName)}`, url: `/${moduleName}`, method: 'DELETE', params: true }
      ]
    })
    
    // 生成Store
    const store = CodeGenerator.generateStore({
      name: moduleName,
      state: {
        list: [],
        total: 0,
        loading: false
      },
      actions: {
        getList: `async function(params) {
  this.loading = true
  try {
    const response = await ${moduleName}List(params)
    this.list = response.data.list
    this.total = response.data.total
  } finally {
    this.loading = false
  }
}`
      }
    })
    
    return {
      listPage,
      formPage,
      apiFile,
      store
    }
  }
  
  // 生成列表页面
  static generateListPage(config) {
    const { moduleName, fields } = config
    
    const tableColumns = fields.map(field => 
      `<el-table-column prop="${field.name}" label="${field.label}" />`
    ).join('\n        ')
    
    return `<template>
  <div class="${toKebabCase(moduleName)}-list">
    <div class="search-form">
      <el-form :model="queryParams" inline>
        ${fields.filter(f => f.searchable).map(field => 
          `<el-form-item label="${field.label}">
          <el-input 
            v-model="queryParams.${field.name}" 
            placeholder="请输入${field.label}" 
          />
        </el-form-item>`
        ).join('\n        ')}
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    
    <div class="table-actions">
      <el-button type="primary" @click="handleAdd">新增</el-button>
      <el-button @click="handleDeleteBatch">批量删除</el-button>
    </div>
    
    <el-table 
      :data="list" 
      v-loading="loading"
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" />
      ${tableColumns}
      <el-table-column label="操作" width="200">
        <template #default="{ row }">
          <el-button size="small" @click="handleEdit(row)">编辑</el-button>
          <el-button size="small" type="danger" @click="handleDelete(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <Pagination
      v-model:page="queryParams.pageNum"
      v-model:limit="queryParams.pageSize"
      :total="total"
      @pagination="getList"
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { ${moduleName}List, delete${capitalize(moduleName)} } from '@/api/${moduleName}'
import Pagination from '@/components/Pagination.vue'

const list = ref([])
const total = ref(0)
const loading = ref(false)
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  ${fields.filter(f => f.searchable).map(f => `${f.name}: ''`).join(',\n  ')}
})

const selectedRows = ref([])

const getList = async () => {
  loading.value = true
  try {
    const response = await ${moduleName}List(queryParams.value)
    list.value = response.data.list
    total.value = response.data.total
  } finally {
    loading.value = false
  }
}

const handleSearch = () => {
  queryParams.value.pageNum = 1
  getList()
}

const handleReset = () => {
  queryParams.value = {
    pageNum: 1,
    pageSize: 10,
    ${fields.filter(f => f.searchable).map(f => `${f.name}: ''`).join(',\n    ')}
  }
  getList()
}

const handleAdd = () => {
  // 跳转到新增页面
}

const handleEdit = (row) => {
  // 跳转到编辑页面
}

const handleDelete = async (id) => {
  try {
    await delete${capitalize(moduleName)}(id)
    ElMessage.success('删除成功')
    getList()
  } catch (error) {
    ElMessage.error('删除失败')
  }
}

const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}

onMounted(() => {
  getList()
})
</script>`
  }
  
  // 生成表单页面
  static generateFormPage(config) {
    const { moduleName, fields } = config
    
    const formItems = fields.filter(f => f.formField).map(field => 
      `<el-form-item label="${field.label}" prop="${field.name}">
        <el-input 
          v-model="formData.${field.name}" 
          placeholder="请输入${field.label}" 
        />
      </el-form-item>`
    ).join('\n        ')
    
    return `<template>
  <div class="${toKebabCase(moduleName)}-form">
    <el-card>
      <template #header>
        <span>${config.title || capitalize(moduleName)}表单</span>
      </template>
      
      <el-form 
        :model="formData" 
        :rules="formRules"
        ref="formRef"
        label-width="100px"
      >
        ${formItems}
        
        <el-form-item>
          <el-button type="primary" @click="handleSubmit">提交</el-button>
          <el-button @click="handleCancel">取消</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { get${capitalize(moduleName)}Info, create${capitalize(moduleName)}, update${capitalize(moduleName)} } from '@/api/${moduleName}'

const route = useRoute()
const router = useRouter()
const formRef = ref()

const formData = reactive({
  ${fields.filter(f => f.formField).map(f => `${f.name}: ${f.type === 'number' ? 0 : f.type === 'boolean' ? false : "''"}`).join(',\n  ')}
})

const formRules = {
  ${fields.filter(f => f.required).map(f => 
    `${f.name}: [{ required: true, message: '请输入${f.label}', trigger: 'blur' }]`
  ).join(',\n  ')}
}

const handleSubmit = async () => {
  try {
    await formRef.value.validate()
    
    if (formData.id) {
      // 更新
      await update${capitalize(moduleName)}(formData)
      ElMessage.success('更新成功')
    } else {
      // 创建
      await create${capitalize(moduleName)}(formData)
      ElMessage.success('创建成功')
    }
    
    router.back()
  } catch (error) {
    ElMessage.error('提交失败')
  }
}

const handleCancel = () => {
  router.back()
}

// 如果是编辑模式,加载数据
if (route.query.id) {
  const loadDetail = async () => {
    const response = await get${capitalize(moduleName)}Info({ id: route.query.id })
    Object.assign(formData, response.data)
  }
  loadDetail()
}
</script>`
  }
}

function toKebabCase(str) {
  return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
}

function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}