代码生成器 美化

This commit is contained in:
cuijiawang
2025-09-25 17:55:52 +08:00
parent 748d4e9e03
commit 944ecfa3bf
3 changed files with 375 additions and 100 deletions

View File

@@ -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 {

View File

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

View File

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