代码生成器 美化
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from "vue";
|
||||
import { ref, watch, nextTick, computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
modelValue: string;
|
||||
@@ -62,14 +62,27 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
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;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 计算动态高度
|
||||
const dynamicHeight = computed(() => {
|
||||
if (props.readonly && props.modelValue) {
|
||||
const lines = props.modelValue.split('\n').length;
|
||||
const lineHeight = 19.5; // 匹配CSS中的line-height
|
||||
const padding = 16; // top + bottom padding
|
||||
const minHeight = 200;
|
||||
const calculatedHeight = lines * lineHeight + padding;
|
||||
return Math.max(calculatedHeight, minHeight) + 'px';
|
||||
}
|
||||
return props.height;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -85,14 +98,14 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
{{ lineNum }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 代码输入区域 -->
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
:style="{ height }"
|
||||
:style="{ height: dynamicHeight }"
|
||||
class="code-textarea"
|
||||
spellcheck="false"
|
||||
@input="updateValue(($event.target as HTMLTextAreaElement).value)"
|
||||
@@ -109,15 +122,15 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
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);
|
||||
@@ -130,7 +143,15 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
overflow: hidden;
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
|
||||
|
||||
// 隐藏滚动条
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE/Edge
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none; // Chrome/Safari/WebKit
|
||||
}
|
||||
|
||||
.line-number {
|
||||
height: 19.5px; // 匹配textarea行高
|
||||
display: flex;
|
||||
@@ -139,7 +160,7 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.code-textarea {
|
||||
flex: 1;
|
||||
border: none;
|
||||
@@ -151,22 +172,40 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
line-height: 1.5;
|
||||
background: transparent;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
|
||||
// 只读模式下自动调整高度
|
||||
&[readonly] {
|
||||
overflow: hidden;
|
||||
min-height: 200px;
|
||||
height: auto !important;
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE/Edge
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none; // Chrome/Safari/WebKit
|
||||
}
|
||||
}
|
||||
|
||||
// 可编辑模式保持滚动
|
||||
&:not([readonly]) {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
// SQL语法高亮的基础样式
|
||||
&[data-language="sql"] {
|
||||
// 这里可以添加SQL关键字高亮等功能
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 暗色主题适配
|
||||
.dark & {
|
||||
.line-numbers {
|
||||
|
||||
@@ -150,48 +150,170 @@ const switchConfig = [
|
||||
<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);
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
|
||||
.section-title {
|
||||
position: relative;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-primary);
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
background: var(--el-bg-color);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.el-input, .el-select {
|
||||
.el-input__wrapper {
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
box-shadow: 0 0 0 1px var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
gap: 12px;
|
||||
|
||||
.switch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: var(--el-bg-color-page);
|
||||
background: linear-gradient(135deg, var(--el-bg-color) 0%, var(--el-bg-color-page) 100%);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--el-color-primary-light-8) 0%, var(--el-color-primary-light-6) 100%);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
|
||||
&::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
:deep(.el-switch) {
|
||||
.el-switch__core {
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&::after {
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked .el-switch__core {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.config-form {
|
||||
.config-section {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
|
||||
.switch-item {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useCodegen } from "./utils/hook";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import TemplateSelector from "./components/TemplateSelector.vue";
|
||||
@@ -8,6 +8,8 @@ import CodeEditor from "./components/CodeEditor.vue";
|
||||
|
||||
import Play from "@iconify-icons/ep/caret-right";
|
||||
import CopyDocument from "@iconify-icons/ep/document-copy";
|
||||
import ArrowDown from "@iconify-icons/ep/arrow-down";
|
||||
import ArrowUp from "@iconify-icons/ep/arrow-up";
|
||||
|
||||
defineOptions({
|
||||
name: "Codegen"
|
||||
@@ -34,6 +36,12 @@ const {
|
||||
switchHistoricalData
|
||||
} = useCodegen();
|
||||
|
||||
// 折叠状态
|
||||
const historyCollapsed = ref(false);
|
||||
const sqlCollapsed = ref(false);
|
||||
const configCollapsed = ref(false);
|
||||
const templateCollapsed = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
loadAllTemplates();
|
||||
});
|
||||
@@ -53,13 +61,75 @@ onMounted(() => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 历史记录区域 -->
|
||||
<el-card v-if="hasHistory" class="mb-4 collapsible-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="collapsible-header" @click="historyCollapsed = !historyCollapsed">
|
||||
<h3 class="text-lg font-semibold">
|
||||
历史记录
|
||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': historyCollapsed }">
|
||||
<component :is="useRenderIcon(ArrowDown)" />
|
||||
</el-icon>
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="!historyCollapsed" class="history-content">
|
||||
<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>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</el-card>
|
||||
|
||||
<!-- 输入SQL区域 -->
|
||||
<el-card class="mb-4" shadow="hover">
|
||||
<el-card class="mb-4 collapsible-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="collapsible-header" @click="sqlCollapsed = !sqlCollapsed">
|
||||
<h3 class="text-lg font-semibold">
|
||||
输入SQL
|
||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': sqlCollapsed }">
|
||||
<component :is="useRenderIcon(ArrowDown)" />
|
||||
</el-icon>
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="!sqlCollapsed">
|
||||
<CodeEditor
|
||||
v-model="formData.tableSql"
|
||||
language="sql"
|
||||
placeholder="请输入表结构SQL..."
|
||||
height="300px"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</el-card>
|
||||
|
||||
<!-- 生成设置区域 -->
|
||||
<el-card class="mb-4 collapsible-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold">输入SQL</h3>
|
||||
<div class="collapsible-header" @click="configCollapsed = !configCollapsed">
|
||||
<h3 class="text-lg font-semibold">
|
||||
生成设置
|
||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': configCollapsed }">
|
||||
<component :is="useRenderIcon(ArrowDown)" />
|
||||
</el-icon>
|
||||
</h3>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
type="primary"
|
||||
:icon="useRenderIcon(Play)"
|
||||
:loading="generating"
|
||||
@click="generateCode"
|
||||
@@ -68,61 +138,43 @@ onMounted(() => {
|
||||
</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-collapse-transition>
|
||||
<div v-show="!configCollapsed">
|
||||
<ConfigForm
|
||||
v-model="formData.options"
|
||||
:data-type-options="dataTypeOptions"
|
||||
:tinyint-trans-type-options="tinyintTransTypeOptions"
|
||||
:time-trans-type-options="timeTransTypeOptions"
|
||||
:name-case-type-options="nameCaseTypeOptions"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</el-card>
|
||||
|
||||
<!-- 模板选择区域 -->
|
||||
<el-card class="mb-4" shadow="hover">
|
||||
<el-card class="mb-4 collapsible-card" shadow="hover">
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold">模板选择</h3>
|
||||
<div class="collapsible-header" @click="templateCollapsed = !templateCollapsed">
|
||||
<h3 class="text-lg font-semibold">
|
||||
模板选择
|
||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': templateCollapsed }">
|
||||
<component :is="useRenderIcon(ArrowDown)" />
|
||||
</el-icon>
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<TemplateSelector
|
||||
:templates="templates"
|
||||
:current-select="currentSelect"
|
||||
:loading="loading"
|
||||
@template-change="setOutputModel"
|
||||
/>
|
||||
|
||||
<el-collapse-transition>
|
||||
<div v-show="!templateCollapsed">
|
||||
<TemplateSelector
|
||||
:templates="templates"
|
||||
:current-select="currentSelect"
|
||||
:loading="loading"
|
||||
@template-change="setOutputModel"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</el-card>
|
||||
|
||||
<!-- 输出代码区域 -->
|
||||
@@ -140,7 +192,7 @@ onMounted(() => {
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<CodeEditor
|
||||
v-model="outputStr"
|
||||
language="java"
|
||||
@@ -163,33 +215,95 @@ onMounted(() => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠卡片样式
|
||||
.collapsible-card {
|
||||
.collapsible-header {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
|
||||
.collapse-arrow {
|
||||
margin-left: 8px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
h3 .collapse-arrow {
|
||||
color: var(--el-color-primary);
|
||||
transform: scale(1.1);
|
||||
|
||||
&.collapsed {
|
||||
transform: rotate(-90deg) scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
|
||||
.el-collapse-transition {
|
||||
.config-form,
|
||||
.template-selector,
|
||||
.history-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 当头部包含按钮时的样式(生成设置区域)
|
||||
.el-card__header {
|
||||
.flex.justify-between.items-center {
|
||||
.collapsible-header {
|
||||
flex: 1;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 历史记录标签样式
|
||||
: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);
|
||||
@@ -201,15 +315,15 @@ onMounted(() => {
|
||||
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%);
|
||||
}
|
||||
@@ -222,7 +336,7 @@ onMounted(() => {
|
||||
.p-4 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user