代码生成器五步向导交互设计
代码生成器五步向导交互设计
引言
代码生成器是低代码平台的核心能力之一,它将数据库表结构转化为可运行的业务代码,大幅减少重复劳动。然而,代码生成器的交互设计直接影响用户体验——步骤过多会让用户迷失,步骤过少则缺乏灵活性。
本文分享一种五步向导式代码生成器交互设计方案:选择数据源 → 选择数据表 → 选择模板集 → 配置字段 → 预览与生成。每一步聚焦一个决策维度,通过自动填充规则减少用户输入,通过在线预览让用户确认结果,最终一键生成高质量的业务代码。
核心内容
一、五步向导整体流程
flowchart LR
A[步骤一
选择数据源] --> B[步骤二
选择数据表]
B --> C[步骤三
选择模板集]
C --> D[步骤四
配置字段]
D --> E[步骤五
预览与生成]
style A fill:#4CAF50,color:#fff
style B fill:#2196F3,color:#fff
style C fill:#FF9800,color:#fff
style D fill:#9C27B0,color:#fff
style E fill:#F44336,color:#fff
二、步骤一:选择数据源
企业级应用通常有多个数据源(主库、从库、业务库等),代码生成器需要支持多数据源切换。
flowchart TB
A[进入代码生成器] --> B[加载数据源列表]
B --> C{有数据源?}
C -->|是| D[展示数据源下拉列表]
C -->|否| E[引导用户配置数据源]
D --> F[用户选择数据源]
F --> G[测试连接]
G --> H{连接成功?}
H -->|是| I[进入下一步]
H -->|否| J[提示连接失败原因]
2.1 数据源选择组件
<template>
<div class="step-datasource">
<el-form :model="form" label-width="100px">
<el-form-item label="数据源">
<el-select
v-model="form.datasourceId"
placeholder="请选择数据源"
@change="onDatasourceChange"
>
<el-option
v-for="ds in datasourceList"
:key="ds.id"
:label="ds.name"
:value="ds.id"
>
<span>{{ ds.name }}</span>
<el-tag size="small" :type="ds.status ? 'success' : 'danger'">
{{ ds.status ? '正常' : '异常' }}
</el-tag>
</el-option>
</el-select>
</el-form-item>
<!-- 数据源连接信息预览 -->
<el-descriptions v-if="currentDatasource" :column="2" border>
<el-descriptions-item label="数据库类型">
{{ currentDatasource.dbType }}
</el-descriptions-item>
<el-descriptions-item label="主机地址">
{{ currentDatasource.host }}:{{ currentDatasource.port }}
</el-descriptions-item>
<el-descriptions-item label="数据库名">
{{ currentDatasource.database }}
</el-descriptions-item>
</el-descriptions>
</el-form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const form = ref({ datasourceId: null })
const datasourceList = ref([])
const currentDatasource = computed(() =>
datasourceList.value.find(d => d.id === form.value.datasourceId)
)
const onDatasourceChange = (id) => {
// 切换数据源时重置后续步骤的数据
emit('datasource-changed', id)
}
</script>
三、步骤二:选择数据表
选定数据源后,加载该数据源下所有表,支持搜索过滤和批量选择。
flowchart TB
A[选择数据源完成] --> B[加载表列表]
B --> C[展示表名+注释+行数]
C --> D[搜索过滤]
D --> E[勾选目标表]
E --> F{选择多表?}
F -->|单表| G[直接进入下一步]
F -->|多表| H[展示表关联关系]
H --> G
3.1 表选择组件
<template>
<div class="step-table-select">
<!-- 搜索栏 -->
<el-input
v-model="searchKeyword"
placeholder="搜索表名或注释"
prefix-icon="Search"
clearable
/>
<!-- 表列表 -->
<el-table
:data="filteredTables"
@selection-change="onSelectionChange"
height="400"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="tableName" label="表名" sortable />
<el-table-column prop="tableComment" label="表注释" />
<el-table-column prop="columnCount" label="字段数" width="80" />
<el-table-column prop="rowCount" label="数据量" width="100" />
</el-table>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const searchKeyword = ref('')
const tableList = ref([])
const selectedTables = ref([])
const filteredTables = computed(() => {
if (!searchKeyword.value) return tableList.value
const keyword = searchKeyword.value.toLowerCase()
return tableList.value.filter(t =>
t.tableName.toLowerCase().includes(keyword) ||
t.tableComment.includes(keyword)
)
})
const onSelectionChange = (selection) => {
selectedTables.value = selection
}
</script>
四、步骤三:选择模板集
模板集决定了生成代码的技术栈和分层结构。不同业务场景可能需要不同的模板集。
flowchart TB
A[选择数据表完成] --> B[加载模板集列表]
B --> C[展示模板集卡片]
C --> D[单表CRUD模板集]
C --> E[主子表模板集]
C --> F[树形结构模板集]
D --> D1[Controller + Service + Mapper + Vue页面]
E --> E1[主表CRUD + 子表关联操作]
F --> F1[树形数据 + 递归查询 + 拖拽排序]
D1 --> G[用户选择模板集]
E1 --> G
F1 --> G
4.1 模板集选择组件
<template>
<div class="step-template">
<el-radio-group v-model="selectedTemplateSet">
<div class="template-cards">
<el-card
v-for="tpl in templateSets"
:key="tpl.id"
class="template-card"
:class="{ active: selectedTemplateSet === tpl.id }"
@click="selectedTemplateSet = tpl.id"
>
<template #header>
<el-radio :value="tpl.id">
{{ tpl.name }}
</el-radio>
</template>
<p>{{ tpl.description }}</p>
<div class="template-tags">
<el-tag v-for="tag in tpl.tags" :key="tag" size="small">
{{ tag }}
</el-tag>
</div>
<!-- 模板包含的文件预览 -->
<div class="template-files">
<span v-for="file in tpl.files" :key="file" class="file-item">
{{ file }}
</span>
</div>
</el-card>
</div>
</el-radio-group>
</div>
</template>
五、步骤四:配置字段
字段配置是五步向导中最核心也最复杂的步骤。关键设计原则:自动填充为主,手动调整为辅。
flowchart TB
A[选择模板集完成] --> B[读取表字段元数据]
B --> C[自动填充字段配置]
C --> D[用户手动调整]
D --> E[确认配置]
subgraph 自动填充规则
C --> C1[Java类型映射]
C --> C2[字段注释→标签]
C --> C3[主键字段→必填+只读]
C --> C4[创建时间→隐藏新增表单]
C --> C5[逻辑删除→隐藏列表]
C --> C6[字典类型推断]
end
subgraph 用户可调整项
D --> D1[是否在列表展示]
D --> D2[是否在表单展示]
D --> D3[表单控件类型]
D --> D4[校验规则]
D --> D5[字典编码]
D --> D6[查询方式]
end
5.1 自动填充规则
/**
* 字段配置自动填充规则
* 根据数据库字段元数据自动推断生成配置
*/
// 数据库类型 → Java 类型映射
const typeMapping = {
'bigint': 'Long',
'int': 'Integer',
'tinyint': 'Integer',
'smallint': 'Integer',
'varchar': 'String',
'char': 'String',
'text': 'String',
'longtext': 'String',
'datetime': 'LocalDateTime',
'date': 'LocalDate',
'decimal': 'BigDecimal',
'float': 'Double',
'double': 'Double'
}
/**
* 自动推断字段配置
* @param {Object} column 数据库字段元数据
* @returns {Object} 自动填充的字段配置
*/
function autoFillConfig(column) {
const config = {
fieldName: toCamelCase(column.columnName),
fieldType: typeMapping[column.dataType] || 'String',
label: column.columnComment || toLabel(column.columnName),
listShow: true,
formShow: true,
formType: inferFormType(column),
required: column.isNullable === 'NO' && !column.isPrimaryKey,
queryType: inferQueryType(column),
dictCode: inferDictCode(column)
}
// 特殊字段规则覆盖
applySpecialRules(config, column)
return config
}
/**
* 特殊字段自动规则
*/
function applySpecialRules(config, column) {
const name = column.columnName.toLowerCase()
// 主键字段
if (column.isPrimaryKey) {
config.listShow = false
config.formShow = false
config.required = false
}
// 创建/更新时间
if (['create_time', 'update_time'].includes(name)) {
config.formShow = false
config.formType = 'datetime'
}
// 逻辑删除字段
if (['del_flag', 'is_deleted'].includes(name)) {
config.listShow = false
config.formShow = false
}
// 创建人/更新人
if (['create_by', 'update_by'].includes(name)) {
config.formShow = false
}
// 备注字段使用文本域
if (['remark', 'description', 'memo'].includes(name)) {
config.formType = 'textarea'
}
// 状态字段推断为下拉选择
if (name.endsWith('_status') || name === 'status') {
config.formType = 'select'
}
}
/**
* 推断表单控件类型
*/
function inferFormType(column) {
const type = column.dataType
if (type.includes('datetime') || type.includes('timestamp')) return 'datetime'
if (type.includes('date')) return 'date'
if (type === 'text' || type === 'longtext') return 'textarea'
if (type === 'tinyint' && column.columnComment?.includes('是')) return 'switch'
if (column.columnName.endsWith('_image') || column.columnName.endsWith('_file')) return 'upload'
return 'input'
}
5.2 字段配置表格
<template>
<div class="step-field-config">
<el-table :data="fieldConfigs" border>
<!-- 字段名 -->
<el-table-column prop="fieldName" label="字段名" width="140" />
<!-- 字段标签(可编辑) -->
<el-table-column label="标签" width="120">
<template #default="{ row }">
<el-input v-model="row.label" size="small" />
</template>
</el-table-column>
<!-- Java类型(可编辑) -->
<el-table-column label="Java类型" width="130">
<template #default="{ row }">
<el-select v-model="row.fieldType" size="small">
<el-option v-for="t in javaTypes" :key="t" :label="t" :value="t" />
</el-select>
</template>
</el-table-column>
<!-- 表单控件类型 -->
<el-table-column label="控件类型" width="120">
<template #default="{ row }">
<el-select v-model="row.formType" size="small">
<el-option label="输入框" value="input" />
<el-option label="文本域" value="textarea" />
<el-option label="下拉选择" value="select" />
<el-option label="日期" value="date" />
<el-option label="日期时间" value="datetime" />
<el-option label="开关" value="switch" />
<el-option label="上传" value="upload" />
</el-select>
</template>
</el-table-column>
<!-- 列表展示 -->
<el-table-column label="列表" width="60" align="center">
<template #default="{ row }">
<el-checkbox v-model="row.listShow" />
</template>
</el-table-column>
<!-- 表单展示 -->
<el-table-column label="表单" width="60" align="center">
<template #default="{ row }">
<el-checkbox v-model="row.formShow" />
</template>
</el-table-column>
<!-- 必填 -->
<el-table-column label="必填" width="60" align="center">
<template #default="{ row }">
<el-checkbox v-model="row.required" />
</template>
</el-table-column>
<!-- 查询方式 -->
<el-table-column label="查询" width="100">
<template #default="{ row }">
<el-select v-model="row.queryType" size="small" clearable>
<el-option label="精确" value="eq" />
<el-option label="模糊" value="like" />
<el-option label="范围" value="between" />
</el-select>
</template>
</el-table-column>
<!-- 字典编码 -->
<el-table-column label="字典" width="130">
<template #default="{ row }">
<el-select v-model="row.dictCode" size="small" clearable filterable>
<el-option
v-for="dict in dictList"
:key="dict.code"
:label="dict.name"
:value="dict.code"
/>
</el-select>
</template>
</el-table-column>
</el-table>
</div>
</template>
六、步骤五:预览与生成
在最终生成前,提供在线代码预览功能,让用户确认生成结果。
flowchart TB
A[字段配置完成] --> B[提交生成请求]
B --> C[后端渲染模板]
C --> D[返回生成结果]
D --> E[代码预览面板]
E --> F{用户确认}
F -->|需要调整| G[返回步骤四修改]
F -->|确认生成| H[选择生成方式]
H --> I[下载ZIP包]
H --> J[直接写入项目目录]
I --> K[生成完成]
J --> K
6.1 代码预览组件
<template>
<div class="step-preview">
<!-- 文件树 -->
<div class="file-tree">
<el-tree
:data="fileTree"
:props="{ label: 'name', children: 'children' }"
@node-click="onFileClick"
/>
</div>
<!-- 代码预览区 -->
<div class="code-preview">
<div class="file-header">
<span>{{ currentFile.path }}</span>
<el-button size="small" @click="copyCode">复制代码</el-button>
</div>
<CodeHighlight
:code="currentFile.content"
:language="getLanguage(currentFile.path)"
/>
</div>
<!-- 底部操作栏 -->
<div class="actions">
<el-button @click="prevStep">上一步</el-button>
<el-button type="primary" @click="downloadZip">下载代码包</el-button>
<el-button type="success" @click="generateToProject">生成到项目</el-button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const fileTree = ref([])
const currentFile = ref({ path: '', content: '' })
/** 根据文件扩展名推断语言 */
const getLanguage = (path) => {
if (path.endsWith('.java')) return 'java'
if (path.endsWith('.vue')) return 'vue'
if (path.endsWith('.xml')) return 'xml'
if (path.endsWith('.sql')) return 'sql'
if (path.endsWith('.js')) return 'javascript'
return 'plaintext'
}
</script>
七、向导状态管理
五步向导涉及多个步骤间的数据传递和状态同步,使用 Pinia 统一管理:
/**
* 代码生成器向导状态管理
*/
import { defineStore } from 'pinia'
export const useGeneratorStore = defineStore('generator', {
state: () => ({
currentStep: 0,
// 步骤一:数据源
datasourceId: null,
// 步骤二:数据表
selectedTables: [],
// 步骤三:模板集
templateSetId: null,
// 步骤四:字段配置
fieldConfigs: {}, // key: tableName, value: FieldConfig[]
// 步骤五:预览结果
previewResult: {}
}),
getters: {
/** 是否可以进入下一步 */
canNext: (state) => {
switch (state.currentStep) {
case 0: return !!state.datasourceId
case 1: return state.selectedTables.length > 0
case 2: return !!state.templateSetId
case 3: return Object.keys(state.fieldConfigs).length > 0
case 4: return true
default: return false
}
}
},
actions: {
/** 重置向导状态 */
reset() {
this.$reset()
},
/** 切换数据源时清除后续步骤数据 */
clearAfterDatasource() {
this.selectedTables = []
this.templateSetId = null
this.fieldConfigs = {}
this.previewResult = {}
}
}
})
八、完整交互流程
sequenceDiagram
participant U as 用户
participant W as 向导组件
participant API as 后端API
participant DB as 数据库
U->>W: 进入代码生成器
W->>API: 获取数据源列表
API-->>W: 返回数据源列表
U->>W: 选择数据源
W->>API: 获取表列表(datasourceId)
API->>DB: 查询表元数据
DB-->>API: 表信息
API-->>W: 返回表列表
U->>W: 选择数据表
W->>API: 获取模板集列表
API-->>W: 返回模板集
U->>W: 选择模板集
W->>API: 获取字段配置(tableName)
API->>DB: 查询字段元数据
DB-->>API: 字段信息
API-->>W: 返回自动填充的配置
U->>W: 调整字段配置
U->>W: 点击预览
W->>API: 提交生成预览请求
API-->>W: 返回生成代码
U->>W: 确认并下载
W->>API: 下载代码包
API-->>U: ZIP文件下载
结论与建议
核心要点
- 五步向导降低认知负担:每一步只做一个决策,用户不会迷失在复杂表单中
- 自动填充减少手动输入:根据数据库元数据自动推断 80% 以上的配置项
- 在线预览提升信心:生成前可预览代码,避免生成后才发现问题
- 状态隔离保证数据一致性:切换数据源时自动清除后续步骤数据
最佳实践建议
| 建议 | 说明 |
|---|---|
| 自动填充优先 | 能自动推断的配置不要让用户手动填写 |
| 支持批量生成 | 多表同时生成时保持配置一致性 |
| 模板可扩展 | 支持自定义模板集,满足不同项目规范 |
| 生成记录可追溯 | 记录每次生成的配置快照,方便回溯 |
| 增量生成 | 支持只生成变化的文件,避免覆盖手动修改 |
| 预览即所见 | 预览代码与最终生成代码完全一致 |