代码生成器 mock数据版

This commit is contained in:
cuijiawang
2025-09-24 17:33:38 +08:00
parent f5d2877e1f
commit a99ba1dda7
7 changed files with 1197 additions and 0 deletions

58
src/api/system/codegen.ts Normal file
View 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
});
}

View 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框架
- 自定义代码编辑器组件
- 响应式布局设计

View 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>

View 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>

View 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>

View 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>

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