代码生成器 mock数据版
This commit is contained in:
58
src/api/system/codegen.ts
Normal file
58
src/api/system/codegen.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { http } from "@/utils/http";
|
||||
|
||||
// 代码生成配置选项
|
||||
export interface CodegenOptions {
|
||||
dataType: string; // sql, select-sql, create-sql, json, insert-sql
|
||||
authorName: string;
|
||||
packageName: string;
|
||||
returnUtilSuccess: string;
|
||||
returnUtilFailure: string;
|
||||
ignorePrefix: string;
|
||||
tinyintTransType: string; // boolean, Boolean, Integer, int, String, Short
|
||||
timeTransType: string; // Date, DateTime, Time, Timestamp, Calendar, LocalDate, LocalDateTime, LocalTime
|
||||
nameCaseType: string; // CamelCase, UnderScoreCase
|
||||
isPackageType: boolean;
|
||||
isSwagger: boolean;
|
||||
isComment: boolean;
|
||||
isAutoImport: boolean;
|
||||
isWithPackage: boolean;
|
||||
isLombok: boolean;
|
||||
}
|
||||
|
||||
// 代码生成请求参数
|
||||
export interface CodegenRequest {
|
||||
tableSql: string;
|
||||
options: CodegenOptions;
|
||||
}
|
||||
|
||||
// 模板信息
|
||||
export interface TemplateInfo {
|
||||
name: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
// 模板组
|
||||
export interface TemplateGroup {
|
||||
group: string;
|
||||
templates: TemplateInfo[];
|
||||
}
|
||||
|
||||
// 代码生成响应
|
||||
export interface CodegenResponse {
|
||||
outputJson: Record<string, string>;
|
||||
tableName: string;
|
||||
}
|
||||
|
||||
// 获取所有模板
|
||||
export function getAllTemplatesApi() {
|
||||
return http.request<ResponseData<{ templates: TemplateGroup[] }>>("post", "/template/all", {
|
||||
data: { id: 1234 }
|
||||
});
|
||||
}
|
||||
|
||||
// 生成代码
|
||||
export function generateCodeApi(data: CodegenRequest) {
|
||||
return http.request<ResponseData<CodegenResponse>>("post", "/code/generate", {
|
||||
data
|
||||
});
|
||||
}
|
||||
109
src/views/system/codegen/README.md
Normal file
109
src/views/system/codegen/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 代码生成器模块
|
||||
|
||||
## 功能概述
|
||||
|
||||
代码生成器模块是一个强大的开发工具,可以根据数据库表结构自动生成各种类型的代码文件,包括实体类、Controller、Service、Mapper等。
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. SQL解析
|
||||
- 支持多种SQL解析引擎
|
||||
- 自研SQL解析引擎(推荐)
|
||||
- JSqlParser引擎支持
|
||||
- JSON格式输入支持
|
||||
|
||||
### 2. 代码模板
|
||||
- **MyBatis-Plus**: Entity、Controller、Service、Mapper
|
||||
- **MyBatis**: 完整的CRUD代码生成
|
||||
- **JPA**: Entity、Controller、Repository
|
||||
- **UI模板**: Element UI、Bootstrap UI、LayUI等
|
||||
|
||||
### 3. 配置选项
|
||||
- 作者信息和包名配置
|
||||
- 数据类型转换配置
|
||||
- 代码风格配置(驼峰、下划线)
|
||||
- 功能开关(Lombok、Swagger、注释等)
|
||||
|
||||
### 4. 高级功能
|
||||
- 历史记录管理
|
||||
- 代码预览和复制
|
||||
- 响应式界面设计
|
||||
- 代码编辑器支持行号显示
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
src/views/system/codegen/
|
||||
├── index.vue # 主页面组件
|
||||
├── utils/
|
||||
│ └── hook.tsx # 业务逻辑hook
|
||||
├── components/
|
||||
│ ├── TemplateSelector.vue # 模板选择组件
|
||||
│ ├── ConfigForm.vue # 配置表单组件
|
||||
│ └── CodeEditor.vue # 代码编辑器组件
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## API接口
|
||||
|
||||
### 获取模板列表
|
||||
```typescript
|
||||
GET /template/all
|
||||
Response: {
|
||||
templates: TemplateGroup[]
|
||||
}
|
||||
```
|
||||
|
||||
### 生成代码
|
||||
```typescript
|
||||
POST /code/generate
|
||||
Request: {
|
||||
tableSql: string,
|
||||
options: CodegenOptions
|
||||
}
|
||||
Response: {
|
||||
outputJson: Record<string, string>,
|
||||
tableName: string
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. **输入SQL**: 在SQL输入框中输入数据库表的DDL语句
|
||||
2. **配置参数**: 根据需要调整生成配置选项
|
||||
3. **选择模板**: 选择需要生成的代码模板类型
|
||||
4. **生成代码**: 点击"生成代码"按钮
|
||||
5. **预览复制**: 在输出区域预览生成的代码,可一键复制
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 基础配置
|
||||
- **解析引擎**: 选择SQL解析方式
|
||||
- **作者**: 生成代码中的作者信息
|
||||
- **包名**: Java包路径
|
||||
|
||||
### 类型转换
|
||||
- **TinyInt转换**: 数据库tinyint类型对应的Java类型
|
||||
- **时间类型**: 时间字段对应的Java类型
|
||||
- **命名类型**: 字段命名风格(驼峰/下划线)
|
||||
|
||||
### 功能开关
|
||||
- **Lombok**: 是否使用Lombok注解
|
||||
- **Swagger**: 是否生成Swagger文档注解
|
||||
- **字段注释**: 是否保留数据库字段注释
|
||||
- **自动引包**: 是否自动添加import语句
|
||||
|
||||
## 扩展说明
|
||||
|
||||
当前版本使用假数据进行演示,实际使用时需要:
|
||||
|
||||
1. 实现后端API接口
|
||||
2. 替换hook中的假数据调用
|
||||
3. 根据实际需求调整模板配置
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Vue 3 + TypeScript
|
||||
- Element Plus UI框架
|
||||
- 自定义代码编辑器组件
|
||||
- 响应式布局设计
|
||||
177
src/views/system/codegen/components/CodeEditor.vue
Normal file
177
src/views/system/codegen/components/CodeEditor.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from "vue";
|
||||
|
||||
interface Props {
|
||||
modelValue: string;
|
||||
placeholder?: string;
|
||||
readonly?: boolean;
|
||||
language?: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:modelValue", value: string): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
placeholder: "请输入内容...",
|
||||
readonly: false,
|
||||
language: "sql",
|
||||
height: "300px"
|
||||
});
|
||||
|
||||
const emits = defineEmits<Emits>();
|
||||
|
||||
const textareaRef = ref<HTMLTextAreaElement>();
|
||||
const lineNumbers = ref<string[]>([]);
|
||||
|
||||
const updateValue = (value: string) => {
|
||||
emits("update:modelValue", value);
|
||||
updateLineNumbers(value);
|
||||
};
|
||||
|
||||
const updateLineNumbers = (content: string) => {
|
||||
const lines = content.split("\n");
|
||||
lineNumbers.value = lines.map((_, index) => String(index + 1).padStart(3, " "));
|
||||
};
|
||||
|
||||
// 监听内容变化,更新行号
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
updateLineNumbers(newValue);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 同步滚动
|
||||
const handleScroll = (event: Event) => {
|
||||
const textarea = event.target as HTMLTextAreaElement;
|
||||
const lineNumbersEl = textarea.parentElement?.querySelector(".line-numbers") as HTMLElement;
|
||||
if (lineNumbersEl) {
|
||||
lineNumbersEl.scrollTop = textarea.scrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理Tab键
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Tab") {
|
||||
event.preventDefault();
|
||||
const textarea = event.target as HTMLTextAreaElement;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const value = textarea.value;
|
||||
const newValue = value.substring(0, start) + " " + value.substring(end);
|
||||
|
||||
updateValue(newValue);
|
||||
|
||||
nextTick(() => {
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 2;
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="code-editor" :class="{ readonly }">
|
||||
<div class="editor-container">
|
||||
<!-- 行号 -->
|
||||
<div class="line-numbers">
|
||||
<div
|
||||
v-for="(lineNum, index) in lineNumbers"
|
||||
:key="index"
|
||||
class="line-number"
|
||||
>
|
||||
{{ lineNum }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 代码输入区域 -->
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
:style="{ height }"
|
||||
class="code-textarea"
|
||||
spellcheck="false"
|
||||
@input="updateValue(($event.target as HTMLTextAreaElement).value)"
|
||||
@scroll="handleScroll"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.code-editor {
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color);
|
||||
|
||||
&.readonly {
|
||||
background: var(--el-disabled-bg-color);
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.line-numbers {
|
||||
background: var(--el-bg-color-page);
|
||||
border-right: 1px solid var(--el-border-color-lighter);
|
||||
padding: 8px 4px;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--el-text-color-secondary);
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
|
||||
.line-number {
|
||||
height: 19.5px; // 匹配textarea行高
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-textarea {
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
padding: 8px 12px;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
background: transparent;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// SQL语法高亮的基础样式
|
||||
&[data-language="sql"] {
|
||||
// 这里可以添加SQL关键字高亮等功能
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色主题适配
|
||||
.dark & {
|
||||
.line-numbers {
|
||||
background: var(--el-bg-color-darker);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
199
src/views/system/codegen/components/ConfigForm.vue
Normal file
199
src/views/system/codegen/components/ConfigForm.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<script setup lang="ts">
|
||||
import type { CodegenOptions } from "@/api/system/codegen";
|
||||
|
||||
interface Props {
|
||||
modelValue: CodegenOptions;
|
||||
dataTypeOptions: Array<{ label: string; value: string }>;
|
||||
tinyintTransTypeOptions: Array<{ label: string; value: string }>;
|
||||
timeTransTypeOptions: Array<{ label: string; value: string }>;
|
||||
nameCaseTypeOptions: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:modelValue", value: CodegenOptions): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emits = defineEmits<Emits>();
|
||||
|
||||
const updateValue = (key: keyof CodegenOptions, value: any) => {
|
||||
emits("update:modelValue", {
|
||||
...props.modelValue,
|
||||
[key]: value
|
||||
});
|
||||
};
|
||||
|
||||
// 配置分组
|
||||
const basicConfig = [
|
||||
{ key: "dataType", label: "解析引擎", type: "select", options: "dataTypeOptions" },
|
||||
{ key: "authorName", label: "作者", type: "input" },
|
||||
{ key: "packageName", label: "包名", type: "input" }
|
||||
];
|
||||
|
||||
const returnConfig = [
|
||||
{ key: "returnUtilSuccess", label: "成功返回", type: "input" },
|
||||
{ key: "returnUtilFailure", label: "失败返回", type: "input" },
|
||||
{ key: "ignorePrefix", label: "忽略前缀", type: "input" }
|
||||
];
|
||||
|
||||
const typeConfig = [
|
||||
{ key: "tinyintTransType", label: "TinyInt转换", type: "select", options: "tinyintTransTypeOptions" },
|
||||
{ key: "timeTransType", label: "时间类型", type: "select", options: "timeTransTypeOptions" },
|
||||
{ key: "nameCaseType", label: "命名类型", type: "select", options: "nameCaseTypeOptions" }
|
||||
];
|
||||
|
||||
const switchConfig = [
|
||||
{ key: "isPackageType", label: "包装类型" },
|
||||
{ key: "isSwagger", label: "Swagger UI" },
|
||||
{ key: "isComment", label: "字段注释" },
|
||||
{ key: "isAutoImport", label: "自动引包" },
|
||||
{ key: "isWithPackage", label: "带包路径" },
|
||||
{ key: "isLombok", label: "Lombok" }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="config-form">
|
||||
<!-- 基础配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="section-title">基础配置</h4>
|
||||
<div class="form-grid">
|
||||
<el-form-item
|
||||
v-for="config in basicConfig"
|
||||
:key="config.key"
|
||||
:label="config.label"
|
||||
>
|
||||
<el-select
|
||||
v-if="config.type === 'select'"
|
||||
:model-value="modelValue[config.key]"
|
||||
class="w-full"
|
||||
@update:model-value="(val) => updateValue(config.key, val)"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in props[config.options]"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-else
|
||||
:model-value="modelValue[config.key]"
|
||||
@update:model-value="(val) => updateValue(config.key, val)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 返回值配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="section-title">返回值配置</h4>
|
||||
<div class="form-grid">
|
||||
<el-form-item
|
||||
v-for="config in returnConfig"
|
||||
:key="config.key"
|
||||
:label="config.label"
|
||||
>
|
||||
<el-input
|
||||
:model-value="modelValue[config.key]"
|
||||
@update:model-value="(val) => updateValue(config.key, val)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 类型转换配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="section-title">类型转换</h4>
|
||||
<div class="form-grid">
|
||||
<el-form-item
|
||||
v-for="config in typeConfig"
|
||||
:key="config.key"
|
||||
:label="config.label"
|
||||
>
|
||||
<el-select
|
||||
:model-value="modelValue[config.key]"
|
||||
class="w-full"
|
||||
@update:model-value="(val) => updateValue(config.key, val)"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in props[config.options]"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 开关配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="section-title">功能开关</h4>
|
||||
<div class="switch-grid">
|
||||
<el-form-item
|
||||
v-for="config in switchConfig"
|
||||
:key="config.key"
|
||||
:label="config.label"
|
||||
class="switch-item"
|
||||
>
|
||||
<el-switch
|
||||
:model-value="modelValue[config.key]"
|
||||
@update:model-value="(val) => updateValue(config.key, val)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.config-form {
|
||||
.config-section {
|
||||
margin-bottom: 32px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--el-color-primary-light-8);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
.switch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: var(--el-bg-color-page);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
117
src/views/system/codegen/components/TemplateSelector.vue
Normal file
117
src/views/system/codegen/components/TemplateSelector.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import type { TemplateGroup, TemplateInfo } from "@/api/system/codegen";
|
||||
|
||||
interface Props {
|
||||
templates: TemplateGroup[];
|
||||
currentSelect: string;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "template-change", template: TemplateInfo): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false
|
||||
});
|
||||
|
||||
const emits = defineEmits<Emits>();
|
||||
|
||||
const handleTemplateClick = (template: TemplateInfo) => {
|
||||
emits("template-change", template);
|
||||
};
|
||||
|
||||
const isEmpty = computed(() => props.templates.length === 0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="loading" class="template-selector">
|
||||
<div v-if="isEmpty && !loading" class="empty-state">
|
||||
<el-empty description="暂无模板数据" />
|
||||
</div>
|
||||
|
||||
<div v-else class="template-groups">
|
||||
<div
|
||||
v-for="group in templates"
|
||||
:key="group.group"
|
||||
class="template-group"
|
||||
>
|
||||
<div class="group-header">
|
||||
<el-tag type="primary" size="large" effect="dark">
|
||||
{{ group.group }}
|
||||
</el-tag>
|
||||
<span class="template-count">{{ group.templates.length }} 个模板</span>
|
||||
</div>
|
||||
|
||||
<div class="template-buttons">
|
||||
<el-button
|
||||
v-for="template in group.templates"
|
||||
:key="template.key"
|
||||
:type="currentSelect === template.key ? 'primary' : 'default'"
|
||||
size="default"
|
||||
class="template-button"
|
||||
@click="handleTemplateClick(template)"
|
||||
>
|
||||
<span class="template-name">{{ template.name }}</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.template-selector {
|
||||
.empty-state {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.template-groups {
|
||||
.template-group {
|
||||
margin-bottom: 24px;
|
||||
padding: 20px;
|
||||
background: var(--el-bg-color-page);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.template-count {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.template-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.template-button {
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.template-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
231
src/views/system/codegen/index.vue
Normal file
231
src/views/system/codegen/index.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { useCodegen } from "./utils/hook";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import TemplateSelector from "./components/TemplateSelector.vue";
|
||||
import ConfigForm from "./components/ConfigForm.vue";
|
||||
import CodeEditor from "./components/CodeEditor.vue";
|
||||
|
||||
import Play from "@iconify-icons/ep/caret-right";
|
||||
import CopyDocument from "@iconify-icons/ep/document-copy";
|
||||
|
||||
defineOptions({
|
||||
name: "Codegen"
|
||||
});
|
||||
|
||||
const {
|
||||
formData,
|
||||
templates,
|
||||
loading,
|
||||
generating,
|
||||
outputStr,
|
||||
currentSelect,
|
||||
historicalData,
|
||||
dataTypeOptions,
|
||||
tinyintTransTypeOptions,
|
||||
timeTransTypeOptions,
|
||||
nameCaseTypeOptions,
|
||||
hasOutput,
|
||||
hasHistory,
|
||||
loadAllTemplates,
|
||||
setOutputModel,
|
||||
generateCode,
|
||||
copyCode,
|
||||
switchHistoricalData
|
||||
} = useCodegen();
|
||||
|
||||
onMounted(() => {
|
||||
loadAllTemplates();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="p-4">
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800 dark:text-white flex items-center">
|
||||
<el-icon class="mr-2"><component :is="useRenderIcon('ep:code')" /></el-icon>
|
||||
代码生成器
|
||||
</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300 mt-2">
|
||||
输入表结构SQL,选择模板,一键生成代码
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 输入SQL区域 -->
|
||||
<el-card class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold">输入SQL</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(Play)"
|
||||
:loading="generating"
|
||||
@click="generateCode"
|
||||
>
|
||||
生成代码
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<CodeEditor
|
||||
v-model="formData.tableSql"
|
||||
language="sql"
|
||||
placeholder="请输入表结构SQL..."
|
||||
height="300px"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 生成设置区域 -->
|
||||
<el-card class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold">生成设置</h3>
|
||||
</template>
|
||||
|
||||
<ConfigForm
|
||||
v-model="formData.options"
|
||||
:data-type-options="dataTypeOptions"
|
||||
:tinyint-trans-type-options="tinyintTransTypeOptions"
|
||||
:time-trans-type-options="timeTransTypeOptions"
|
||||
:name-case-type-options="nameCaseTypeOptions"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 历史记录区域 -->
|
||||
<el-card v-if="hasHistory" class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold">历史记录</h3>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<el-tag
|
||||
v-for="tableName in historicalData"
|
||||
:key="tableName"
|
||||
type="info"
|
||||
class="cursor-pointer hover:bg-blue-100"
|
||||
@click="switchHistoricalData(tableName)"
|
||||
>
|
||||
{{ tableName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 模板选择区域 -->
|
||||
<el-card class="mb-4" shadow="hover">
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold">模板选择</h3>
|
||||
</template>
|
||||
|
||||
<TemplateSelector
|
||||
:templates="templates"
|
||||
:current-select="currentSelect"
|
||||
:loading="loading"
|
||||
@template-change="setOutputModel"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 输出代码区域 -->
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold">输出代码</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(CopyDocument)"
|
||||
:disabled="!hasOutput"
|
||||
@click="copyCode"
|
||||
>
|
||||
复制代码
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<CodeEditor
|
||||
v-model="outputStr"
|
||||
language="java"
|
||||
placeholder="生成的代码将在这里显示..."
|
||||
height="500px"
|
||||
readonly
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.main {
|
||||
min-height: calc(100vh - 200px);
|
||||
background: var(--el-bg-color-page);
|
||||
}
|
||||
|
||||
:deep(.el-card) {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.el-card__header {
|
||||
background: linear-gradient(135deg, var(--el-color-primary-light-9) 0%, var(--el-bg-color) 100%);
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// 历史记录标签样式
|
||||
:deep(.el-tag) {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮样式优化
|
||||
:deep(.el-button) {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&.el-button--primary {
|
||||
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-dark-2) 100%);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, var(--el-color-primary-light-2) 0%, var(--el-color-primary) 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.main {
|
||||
.p-4 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
306
src/views/system/codegen/utils/hook.tsx
Normal file
306
src/views/system/codegen/utils/hook.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
getAllTemplatesApi,
|
||||
generateCodeApi,
|
||||
CodegenOptions,
|
||||
TemplateGroup,
|
||||
CodegenRequest
|
||||
} from "@/api/system/codegen";
|
||||
|
||||
export function useCodegen() {
|
||||
// 表单数据
|
||||
const formData = reactive<CodegenRequest>({
|
||||
tableSql: `CREATE TABLE 'sys_user_info' (
|
||||
'user_id' int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
|
||||
'user_name' varchar(255) NOT NULL COMMENT '用户名',
|
||||
'status' tinyint(1) NOT NULL COMMENT '状态',
|
||||
'create_time' datetime NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY ('user_id')
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息'`,
|
||||
options: {
|
||||
dataType: "sql",
|
||||
authorName: "AgileBoot",
|
||||
packageName: "com.agileboot.domain",
|
||||
returnUtilSuccess: "ResponseResult.success()",
|
||||
returnUtilFailure: "ResponseResult.error()",
|
||||
ignorePrefix: "sys_",
|
||||
tinyintTransType: "int",
|
||||
timeTransType: "Date",
|
||||
nameCaseType: "CamelCase",
|
||||
isPackageType: true,
|
||||
isSwagger: false,
|
||||
isComment: true,
|
||||
isAutoImport: false,
|
||||
isWithPackage: false,
|
||||
isLombok: true
|
||||
}
|
||||
});
|
||||
|
||||
// 模板数据
|
||||
const templates = ref<TemplateGroup[]>([]);
|
||||
const loading = ref(false);
|
||||
const generating = ref(false);
|
||||
|
||||
// 输出相关
|
||||
const outputStr = ref("");
|
||||
const outputJson = ref<Record<string, string>>({});
|
||||
const currentSelect = ref("plusentity");
|
||||
|
||||
// 历史记录
|
||||
const historicalData = ref<string[]>([]);
|
||||
|
||||
// 数据类型选项
|
||||
const dataTypeOptions = [
|
||||
{ label: "DDL SQL@自研SQL解析引擎", value: "sql" },
|
||||
{ label: "SELECT SQL@JSqlParser引擎", value: "select-sql" },
|
||||
{ label: "CREATE SQL@JSqlParser引擎", value: "create-sql" },
|
||||
{ label: "JSON(Beta)", value: "json" },
|
||||
{ label: "INSERT SQL", value: "insert-sql" }
|
||||
];
|
||||
|
||||
// TinyInt转换类型选项
|
||||
const tinyintTransTypeOptions = [
|
||||
{ label: "boolean", value: "boolean" },
|
||||
{ label: "Boolean", value: "Boolean" },
|
||||
{ label: "Integer", value: "Integer" },
|
||||
{ label: "int", value: "int" },
|
||||
{ label: "String", value: "String" },
|
||||
{ label: "Short", value: "Short" }
|
||||
];
|
||||
|
||||
// 时间类型选项
|
||||
const timeTransTypeOptions = [
|
||||
{ label: "Date", value: "Date" },
|
||||
{ label: "DateTime", value: "DateTime" },
|
||||
{ label: "Time", value: "Time" },
|
||||
{ label: "Timestamp", value: "Timestamp" },
|
||||
{ label: "Calendar", value: "Calendar" },
|
||||
{ label: "LocalDate", value: "LocalDate" },
|
||||
{ label: "LocalDateTime", value: "LocalDateTime" },
|
||||
{ label: "LocalTime", value: "LocalTime" }
|
||||
];
|
||||
|
||||
// 命名类型选项
|
||||
const nameCaseTypeOptions = [
|
||||
{ label: "驼峰", value: "CamelCase" },
|
||||
{ label: "下划线", value: "UnderScoreCase" }
|
||||
];
|
||||
|
||||
// 加载所有模板
|
||||
async function loadAllTemplates() {
|
||||
try {
|
||||
loading.value = true;
|
||||
// 临时使用假数据
|
||||
templates.value = [
|
||||
{
|
||||
group: "MyBatis-Plus",
|
||||
templates: [
|
||||
{ name: "Entity", key: "plusentity" },
|
||||
{ name: "Controller", key: "pluscontroller" },
|
||||
{ name: "Service", key: "plusservice" },
|
||||
{ name: "Mapper", key: "plusmapper" }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: "MyBatis",
|
||||
templates: [
|
||||
{ name: "Entity", key: "model" },
|
||||
{ name: "Controller", key: "controller" },
|
||||
{ name: "Service", key: "service" },
|
||||
{ name: "ServiceImpl", key: "service_impl" },
|
||||
{ name: "Mapper", key: "mapper" },
|
||||
{ name: "Mapper.xml", key: "mybatis" }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: "JPA",
|
||||
templates: [
|
||||
{ name: "Entity", key: "entity" },
|
||||
{ name: "Controller", key: "jpacontroller" },
|
||||
{ name: "Repository", key: "repository" }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: "UI模板",
|
||||
templates: [
|
||||
{ name: "Element UI", key: "element-ui" },
|
||||
{ name: "Bootstrap UI", key: "bootstrap-ui" },
|
||||
{ name: "LayUI List", key: "layui-list" },
|
||||
{ name: "LayUI Edit", key: "layui-edit" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 后续替换为真实API调用
|
||||
// const { data } = await getAllTemplatesApi();
|
||||
// templates.value = data.templates;
|
||||
} catch (error) {
|
||||
console.error("加载模板失败:", error);
|
||||
ElMessage.error("加载模板失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置输出模板
|
||||
function setOutputModel(template: { name: string; key: string }) {
|
||||
currentSelect.value = template.key;
|
||||
if (outputStr.value.length > 30) {
|
||||
outputStr.value = outputJson.value[template.key] || "";
|
||||
}
|
||||
}
|
||||
|
||||
// 生成代码
|
||||
async function generateCode() {
|
||||
try {
|
||||
generating.value = true;
|
||||
|
||||
// 临时使用假数据
|
||||
const fakeResponse = {
|
||||
outputJson: {
|
||||
"plusentity": `@Data
|
||||
@Entity
|
||||
@Table(name = "sys_user_info")
|
||||
public class SysUserInfo {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer userId;
|
||||
|
||||
@Column(name = "user_name")
|
||||
private String userName;
|
||||
|
||||
@Column(name = "status")
|
||||
private Integer status;
|
||||
|
||||
@Column(name = "create_time")
|
||||
private Date createTime;
|
||||
}`,
|
||||
"pluscontroller": `@RestController
|
||||
@RequestMapping("/system/userInfo")
|
||||
public class SysUserInfoController {
|
||||
|
||||
@Autowired
|
||||
private SysUserInfoService sysUserInfoService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResponseResult list() {
|
||||
return ResponseResult.success();
|
||||
}
|
||||
}`,
|
||||
"plusservice": `public interface SysUserInfoService {
|
||||
// Service methods
|
||||
}`,
|
||||
"plusmapper": `@Mapper
|
||||
public interface SysUserInfoMapper {
|
||||
// Mapper methods
|
||||
}`
|
||||
},
|
||||
tableName: "sys_user_info"
|
||||
};
|
||||
|
||||
outputJson.value = fakeResponse.outputJson;
|
||||
outputStr.value = fakeResponse.outputJson[currentSelect.value] || "";
|
||||
|
||||
// 添加到历史记录
|
||||
setHistoricalData(fakeResponse.tableName);
|
||||
|
||||
ElMessage.success("生成成功");
|
||||
|
||||
// 后续替换为真实API调用
|
||||
// const { data } = await generateCodeApi(formData);
|
||||
// outputJson.value = data.outputJson;
|
||||
// outputStr.value = data.outputJson[currentSelect.value] || "";
|
||||
// setHistoricalData(data.tableName);
|
||||
} catch (error) {
|
||||
console.error("生成代码失败:", error);
|
||||
ElMessage.error("生成失败,请检查SQL语句");
|
||||
} finally {
|
||||
generating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 复制代码
|
||||
function copyCode() {
|
||||
if (!outputStr.value) {
|
||||
ElMessage.warning("没有可复制的内容");
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(outputStr.value.trim()).then(() => {
|
||||
ElMessage.success("已复制到剪贴板");
|
||||
}).catch(() => {
|
||||
ElMessage.error("复制失败");
|
||||
});
|
||||
}
|
||||
|
||||
// 设置历史记录
|
||||
function setHistoricalData(tableName: string) {
|
||||
// 添加新表名(如果不存在)
|
||||
if (historicalData.value.indexOf(tableName) < 0) {
|
||||
historicalData.value.unshift(tableName);
|
||||
}
|
||||
|
||||
// 保持最多9个历史记录
|
||||
if (historicalData.value.length > 9) {
|
||||
historicalData.value.splice(9, 1);
|
||||
}
|
||||
|
||||
// 存储到sessionStorage
|
||||
if (window.sessionStorage) {
|
||||
const existingData = sessionStorage.getItem(tableName);
|
||||
if (existingData) {
|
||||
sessionStorage.removeItem(tableName);
|
||||
}
|
||||
sessionStorage.setItem(tableName, JSON.stringify(outputJson.value));
|
||||
}
|
||||
}
|
||||
|
||||
// 切换历史记录
|
||||
function switchHistoricalData(tableName: string) {
|
||||
if (window.sessionStorage) {
|
||||
const valueSession = sessionStorage.getItem(tableName);
|
||||
if (valueSession) {
|
||||
outputJson.value = JSON.parse(valueSession);
|
||||
outputStr.value = outputJson.value[currentSelect.value] || "";
|
||||
ElMessage.success(`切换历史记录成功: ${tableName}`);
|
||||
} else {
|
||||
ElMessage.warning("历史记录不存在");
|
||||
}
|
||||
} else {
|
||||
ElMessage.error("浏览器不支持sessionStorage");
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const hasOutput = computed(() => outputStr.value.length > 0);
|
||||
const hasHistory = computed(() => historicalData.value.length > 0);
|
||||
|
||||
return {
|
||||
// 数据
|
||||
formData,
|
||||
templates,
|
||||
loading,
|
||||
generating,
|
||||
outputStr,
|
||||
currentSelect,
|
||||
historicalData,
|
||||
|
||||
// 选项
|
||||
dataTypeOptions,
|
||||
tinyintTransTypeOptions,
|
||||
timeTransTypeOptions,
|
||||
nameCaseTypeOptions,
|
||||
|
||||
// 计算属性
|
||||
hasOutput,
|
||||
hasHistory,
|
||||
|
||||
// 方法
|
||||
loadAllTemplates,
|
||||
setOutputModel,
|
||||
generateCode,
|
||||
copyCode,
|
||||
switchHistoricalData
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user