feat: 代码生成器 下载功能
This commit is contained in:
parent
944ecfa3bf
commit
2fbdaedf21
@ -36,20 +36,20 @@
|
|||||||
"@vueuse/motion": "^2.0.0",
|
"@vueuse/motion": "^2.0.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
"cropperjs": "^1.5.13",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dayjs": "^1.11.8",
|
"dayjs": "^1.11.8",
|
||||||
"echarts": "^5.4.2",
|
"echarts": "^5.4.2",
|
||||||
"element-plus": "2.3.6",
|
"element-plus": "2.3.6",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
"pinyin-pro": "^3.15.2",
|
"pinyin-pro": "^3.15.2",
|
||||||
"cropperjs": "^1.5.13",
|
|
||||||
"vue-tippy": "^6.2.0",
|
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"qs": "^6.11.2",
|
"qs": "^6.11.2",
|
||||||
"responsive-storage": "^2.2.0",
|
"responsive-storage": "^2.2.0",
|
||||||
@ -57,6 +57,7 @@
|
|||||||
"typeit": "^8.7.1",
|
"typeit": "^8.7.1",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.2",
|
"vue-router": "^4.2.2",
|
||||||
|
"vue-tippy": "^6.2.0",
|
||||||
"vue-types": "^5.1.0",
|
"vue-types": "^5.1.0",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
|
|||||||
11706
pnpm-lock.yaml
generated
11706
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -32,13 +32,15 @@ const updateValue = (value: string) => {
|
|||||||
|
|
||||||
const updateLineNumbers = (content: string) => {
|
const updateLineNumbers = (content: string) => {
|
||||||
const lines = content.split("\n");
|
const lines = content.split("\n");
|
||||||
lineNumbers.value = lines.map((_, index) => String(index + 1).padStart(3, " "));
|
lineNumbers.value = lines.map((_, index) =>
|
||||||
|
String(index + 1).padStart(3, " ")
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听内容变化,更新行号
|
// 监听内容变化,更新行号
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newValue) => {
|
newValue => {
|
||||||
updateLineNumbers(newValue);
|
updateLineNumbers(newValue);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
@ -47,7 +49,9 @@ watch(
|
|||||||
// 同步滚动
|
// 同步滚动
|
||||||
const handleScroll = (event: Event) => {
|
const handleScroll = (event: Event) => {
|
||||||
const textarea = event.target as HTMLTextAreaElement;
|
const textarea = event.target as HTMLTextAreaElement;
|
||||||
const lineNumbersEl = textarea.parentElement?.querySelector(".line-numbers") as HTMLElement;
|
const lineNumbersEl = textarea.parentElement?.querySelector(
|
||||||
|
".line-numbers"
|
||||||
|
) as HTMLElement;
|
||||||
if (lineNumbersEl) {
|
if (lineNumbersEl) {
|
||||||
lineNumbersEl.scrollTop = textarea.scrollTop;
|
lineNumbersEl.scrollTop = textarea.scrollTop;
|
||||||
}
|
}
|
||||||
@ -74,12 +78,12 @@ const handleKeydown = (event: KeyboardEvent) => {
|
|||||||
// 计算动态高度
|
// 计算动态高度
|
||||||
const dynamicHeight = computed(() => {
|
const dynamicHeight = computed(() => {
|
||||||
if (props.readonly && props.modelValue) {
|
if (props.readonly && props.modelValue) {
|
||||||
const lines = props.modelValue.split('\n').length;
|
const lines = props.modelValue.split("\n").length;
|
||||||
const lineHeight = 19.5; // 匹配CSS中的line-height
|
const lineHeight = 19.5; // 匹配CSS中的line-height
|
||||||
const padding = 16; // top + bottom padding
|
const padding = 16; // top + bottom padding
|
||||||
const minHeight = 200;
|
const minHeight = 200;
|
||||||
const calculatedHeight = lines * lineHeight + padding;
|
const calculatedHeight = lines * lineHeight + padding;
|
||||||
return Math.max(calculatedHeight, minHeight) + 'px';
|
return Math.max(calculatedHeight, minHeight) + "px";
|
||||||
}
|
}
|
||||||
return props.height;
|
return props.height;
|
||||||
});
|
});
|
||||||
@ -118,31 +122,31 @@ const dynamicHeight = computed(() => {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.code-editor {
|
.code-editor {
|
||||||
border: 1px solid var(--el-border-color);
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--el-bg-color);
|
background: var(--el-bg-color);
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
&.readonly {
|
&.readonly {
|
||||||
background: var(--el-disabled-bg-color);
|
background: var(--el-disabled-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
display: flex;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
.line-numbers {
|
.line-numbers {
|
||||||
background: var(--el-bg-color-page);
|
min-width: 40px;
|
||||||
border-right: 1px solid var(--el-border-color-lighter);
|
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
overflow: hidden;
|
||||||
|
font-family: Consolas, Monaco, "Courier New", monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
user-select: none;
|
|
||||||
overflow: hidden;
|
|
||||||
min-width: 40px;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
user-select: none;
|
||||||
|
background: var(--el-bg-color-page);
|
||||||
|
border-right: 1px solid var(--el-border-color-lighter);
|
||||||
|
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
scrollbar-width: none; // Firefox
|
scrollbar-width: none; // Firefox
|
||||||
@ -153,31 +157,31 @@ const dynamicHeight = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.line-number {
|
.line-number {
|
||||||
height: 19.5px; // 匹配textarea行高
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
height: 19.5px; // 匹配textarea行高
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-textarea {
|
.code-textarea {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
resize: none;
|
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
font-family: Consolas, Monaco, "Courier New", monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
background: transparent;
|
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
|
resize: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
// 只读模式下自动调整高度
|
// 只读模式下自动调整高度
|
||||||
&[readonly] {
|
&[readonly] {
|
||||||
overflow: hidden;
|
|
||||||
min-height: 200px;
|
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
scrollbar-width: none; // Firefox
|
scrollbar-width: none; // Firefox
|
||||||
-ms-overflow-style: none; // IE/Edge
|
-ms-overflow-style: none; // IE/Edge
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,12 @@ const updateValue = (key: keyof CodegenOptions, value: any) => {
|
|||||||
|
|
||||||
// 配置分组
|
// 配置分组
|
||||||
const basicConfig = [
|
const basicConfig = [
|
||||||
{ key: "dataType", label: "解析引擎", type: "select", options: "dataTypeOptions" },
|
{
|
||||||
|
key: "dataType",
|
||||||
|
label: "解析引擎",
|
||||||
|
type: "select",
|
||||||
|
options: "dataTypeOptions"
|
||||||
|
},
|
||||||
{ key: "authorName", label: "作者", type: "input" },
|
{ key: "authorName", label: "作者", type: "input" },
|
||||||
{ key: "packageName", label: "包名", type: "input" }
|
{ key: "packageName", label: "包名", type: "input" }
|
||||||
];
|
];
|
||||||
@ -37,9 +42,24 @@ const returnConfig = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const typeConfig = [
|
const typeConfig = [
|
||||||
{ key: "tinyintTransType", label: "TinyInt转换", type: "select", options: "tinyintTransTypeOptions" },
|
{
|
||||||
{ key: "timeTransType", label: "时间类型", type: "select", options: "timeTransTypeOptions" },
|
key: "tinyintTransType",
|
||||||
{ key: "nameCaseType", label: "命名类型", type: "select", options: "nameCaseTypeOptions" }
|
label: "TinyInt转换",
|
||||||
|
type: "select",
|
||||||
|
options: "tinyintTransTypeOptions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "timeTransType",
|
||||||
|
label: "时间类型",
|
||||||
|
type: "select",
|
||||||
|
options: "timeTransTypeOptions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "nameCaseType",
|
||||||
|
label: "命名类型",
|
||||||
|
type: "select",
|
||||||
|
options: "nameCaseTypeOptions"
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const switchConfig = [
|
const switchConfig = [
|
||||||
@ -67,7 +87,7 @@ const switchConfig = [
|
|||||||
v-if="config.type === 'select'"
|
v-if="config.type === 'select'"
|
||||||
:model-value="modelValue[config.key]"
|
:model-value="modelValue[config.key]"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@update:model-value="(val) => updateValue(config.key, val)"
|
@update:model-value="val => updateValue(config.key, val)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="option in props[config.options]"
|
v-for="option in props[config.options]"
|
||||||
@ -79,7 +99,7 @@ const switchConfig = [
|
|||||||
<el-input
|
<el-input
|
||||||
v-else
|
v-else
|
||||||
:model-value="modelValue[config.key]"
|
:model-value="modelValue[config.key]"
|
||||||
@update:model-value="(val) => updateValue(config.key, val)"
|
@update:model-value="val => updateValue(config.key, val)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@ -96,7 +116,7 @@ const switchConfig = [
|
|||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="modelValue[config.key]"
|
:model-value="modelValue[config.key]"
|
||||||
@update:model-value="(val) => updateValue(config.key, val)"
|
@update:model-value="val => updateValue(config.key, val)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@ -114,7 +134,7 @@ const switchConfig = [
|
|||||||
<el-select
|
<el-select
|
||||||
:model-value="modelValue[config.key]"
|
:model-value="modelValue[config.key]"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@update:model-value="(val) => updateValue(config.key, val)"
|
@update:model-value="val => updateValue(config.key, val)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="option in props[config.options]"
|
v-for="option in props[config.options]"
|
||||||
@ -139,7 +159,7 @@ const switchConfig = [
|
|||||||
>
|
>
|
||||||
<el-switch
|
<el-switch
|
||||||
:model-value="modelValue[config.key]"
|
:model-value="modelValue[config.key]"
|
||||||
@update:model-value="(val) => updateValue(config.key, val)"
|
@update:model-value="val => updateValue(config.key, val)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@ -148,6 +168,31 @@ const switchConfig = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
// 响应式设计
|
||||||
|
@media (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.config-form {
|
.config-form {
|
||||||
.config-section {
|
.config-section {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
@ -158,22 +203,26 @@ const switchConfig = [
|
|||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding-left: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
margin-bottom: 16px;
|
|
||||||
padding-left: 12px;
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
left: 0;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%);
|
content: "";
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--el-color-primary) 0%,
|
||||||
|
var(--el-color-primary-light-3) 100%
|
||||||
|
);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,27 +232,28 @@ const switchConfig = [
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
||||||
:deep(.el-form-item) {
|
:deep(.el-form-item) {
|
||||||
|
padding: 16px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
background: var(--el-bg-color);
|
background: var(--el-bg-color);
|
||||||
padding: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid var(--el-border-color-lighter);
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 8px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--el-color-primary-light-5);
|
border-color: var(--el-color-primary-light-5);
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 12px rgb(0 0 0 / 8%);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
font-weight: 500;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input, .el-select {
|
.el-input,
|
||||||
|
.el-select {
|
||||||
.el-input__wrapper {
|
.el-input__wrapper {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
@ -230,33 +280,41 @@ const switchConfig = [
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
.switch-item {
|
.switch-item {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
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;
|
overflow: hidden;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--el-bg-color) 0%,
|
||||||
|
var(--el-bg-color-page) 100%
|
||||||
|
);
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
|
left: 0;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background: linear-gradient(90deg, var(--el-color-primary-light-8) 0%, var(--el-color-primary-light-6) 100%);
|
content: "";
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--el-color-primary-light-8) 0%,
|
||||||
|
var(--el-color-primary-light-6) 100%
|
||||||
|
);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
transform: scaleX(0);
|
transform: scaleX(0);
|
||||||
transform-origin: left;
|
transform-origin: left;
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--el-color-primary-light-5);
|
border-color: var(--el-color-primary-light-5);
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@ -266,9 +324,9 @@ const switchConfig = [
|
|||||||
|
|
||||||
:deep(.el-form-item__label) {
|
:deep(.el-form-item__label) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-form-item__content) {
|
:deep(.el-form-item__content) {
|
||||||
@ -293,29 +351,4 @@ const switchConfig = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应式设计
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -8,8 +8,9 @@ import CodeEditor from "./components/CodeEditor.vue";
|
|||||||
|
|
||||||
import Play from "@iconify-icons/ep/caret-right";
|
import Play from "@iconify-icons/ep/caret-right";
|
||||||
import CopyDocument from "@iconify-icons/ep/document-copy";
|
import CopyDocument from "@iconify-icons/ep/document-copy";
|
||||||
|
import Download from "@iconify-icons/ep/download";
|
||||||
|
import FolderOpened from "@iconify-icons/ep/folder-opened";
|
||||||
import ArrowDown from "@iconify-icons/ep/arrow-down";
|
import ArrowDown from "@iconify-icons/ep/arrow-down";
|
||||||
import ArrowUp from "@iconify-icons/ep/arrow-up";
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Codegen"
|
name: "Codegen"
|
||||||
@ -33,6 +34,8 @@ const {
|
|||||||
setOutputModel,
|
setOutputModel,
|
||||||
generateCode,
|
generateCode,
|
||||||
copyCode,
|
copyCode,
|
||||||
|
downloadCurrentCode,
|
||||||
|
downloadAllCode,
|
||||||
switchHistoricalData
|
switchHistoricalData
|
||||||
} = useCodegen();
|
} = useCodegen();
|
||||||
|
|
||||||
@ -52,8 +55,12 @@ onMounted(() => {
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<!-- 页面标题 -->
|
<!-- 页面标题 -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-2xl font-bold text-gray-800 dark:text-white flex items-center">
|
<h1
|
||||||
<el-icon class="mr-2"><component :is="useRenderIcon('ep:code')" /></el-icon>
|
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>
|
</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-300 mt-2">
|
<p class="text-gray-600 dark:text-gray-300 mt-2">
|
||||||
@ -64,10 +71,16 @@ onMounted(() => {
|
|||||||
<!-- 历史记录区域 -->
|
<!-- 历史记录区域 -->
|
||||||
<el-card v-if="hasHistory" class="mb-4 collapsible-card" shadow="hover">
|
<el-card v-if="hasHistory" class="mb-4 collapsible-card" shadow="hover">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="collapsible-header" @click="historyCollapsed = !historyCollapsed">
|
<div
|
||||||
|
class="collapsible-header"
|
||||||
|
@click="historyCollapsed = !historyCollapsed"
|
||||||
|
>
|
||||||
<h3 class="text-lg font-semibold">
|
<h3 class="text-lg font-semibold">
|
||||||
历史记录
|
历史记录
|
||||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': historyCollapsed }">
|
<el-icon
|
||||||
|
class="collapse-arrow"
|
||||||
|
:class="{ collapsed: historyCollapsed }"
|
||||||
|
>
|
||||||
<component :is="useRenderIcon(ArrowDown)" />
|
<component :is="useRenderIcon(ArrowDown)" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</h3>
|
</h3>
|
||||||
@ -97,7 +110,10 @@ onMounted(() => {
|
|||||||
<div class="collapsible-header" @click="sqlCollapsed = !sqlCollapsed">
|
<div class="collapsible-header" @click="sqlCollapsed = !sqlCollapsed">
|
||||||
<h3 class="text-lg font-semibold">
|
<h3 class="text-lg font-semibold">
|
||||||
输入SQL
|
输入SQL
|
||||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': sqlCollapsed }">
|
<el-icon
|
||||||
|
class="collapse-arrow"
|
||||||
|
:class="{ collapsed: sqlCollapsed }"
|
||||||
|
>
|
||||||
<component :is="useRenderIcon(ArrowDown)" />
|
<component :is="useRenderIcon(ArrowDown)" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</h3>
|
</h3>
|
||||||
@ -120,10 +136,16 @@ onMounted(() => {
|
|||||||
<el-card class="mb-4 collapsible-card" shadow="hover">
|
<el-card class="mb-4 collapsible-card" shadow="hover">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="collapsible-header" @click="configCollapsed = !configCollapsed">
|
<div
|
||||||
|
class="collapsible-header"
|
||||||
|
@click="configCollapsed = !configCollapsed"
|
||||||
|
>
|
||||||
<h3 class="text-lg font-semibold">
|
<h3 class="text-lg font-semibold">
|
||||||
生成设置
|
生成设置
|
||||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': configCollapsed }">
|
<el-icon
|
||||||
|
class="collapse-arrow"
|
||||||
|
:class="{ collapsed: configCollapsed }"
|
||||||
|
>
|
||||||
<component :is="useRenderIcon(ArrowDown)" />
|
<component :is="useRenderIcon(ArrowDown)" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</h3>
|
</h3>
|
||||||
@ -155,10 +177,16 @@ onMounted(() => {
|
|||||||
<!-- 模板选择区域 -->
|
<!-- 模板选择区域 -->
|
||||||
<el-card class="mb-4 collapsible-card" shadow="hover">
|
<el-card class="mb-4 collapsible-card" shadow="hover">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="collapsible-header" @click="templateCollapsed = !templateCollapsed">
|
<div
|
||||||
|
class="collapsible-header"
|
||||||
|
@click="templateCollapsed = !templateCollapsed"
|
||||||
|
>
|
||||||
<h3 class="text-lg font-semibold">
|
<h3 class="text-lg font-semibold">
|
||||||
模板选择
|
模板选择
|
||||||
<el-icon class="collapse-arrow" :class="{ 'collapsed': templateCollapsed }">
|
<el-icon
|
||||||
|
class="collapse-arrow"
|
||||||
|
:class="{ collapsed: templateCollapsed }"
|
||||||
|
>
|
||||||
<component :is="useRenderIcon(ArrowDown)" />
|
<component :is="useRenderIcon(ArrowDown)" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</h3>
|
</h3>
|
||||||
@ -182,14 +210,32 @@ onMounted(() => {
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h3 class="text-lg font-semibold">输出代码</h3>
|
<h3 class="text-lg font-semibold">输出代码</h3>
|
||||||
<el-button
|
<div class="flex gap-2">
|
||||||
type="primary"
|
<el-button
|
||||||
:icon="useRenderIcon(CopyDocument)"
|
type="success"
|
||||||
:disabled="!hasOutput"
|
:icon="useRenderIcon(Download)"
|
||||||
@click="copyCode"
|
:disabled="!hasOutput"
|
||||||
>
|
@click="downloadCurrentCode"
|
||||||
复制代码
|
>
|
||||||
</el-button>
|
下载当前文件
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
:icon="useRenderIcon(FolderOpened)"
|
||||||
|
:disabled="!hasOutput"
|
||||||
|
@click="downloadAllCode"
|
||||||
|
>
|
||||||
|
下载所有文件
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="useRenderIcon(CopyDocument)"
|
||||||
|
:disabled="!hasOutput"
|
||||||
|
@click="copyCode"
|
||||||
|
>
|
||||||
|
复制代码
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -206,6 +252,19 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
// 响应式设计
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.main {
|
||||||
|
.p-4 {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
min-height: calc(100vh - 200px);
|
min-height: calc(100vh - 200px);
|
||||||
background: var(--el-bg-color-page);
|
background: var(--el-bg-color-page);
|
||||||
@ -213,21 +272,25 @@ onMounted(() => {
|
|||||||
|
|
||||||
:deep(.el-card) {
|
:deep(.el-card) {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px 0 rgb(0 0 0 / 15%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-card__header {
|
.el-card__header {
|
||||||
background: linear-gradient(135deg, var(--el-color-primary-light-9) 0%, var(--el-bg-color) 100%);
|
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);
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--el-color-primary);
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,8 +312,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
.collapse-arrow {
|
.collapse-arrow {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
@ -305,15 +368,15 @@ onMounted(() => {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按钮样式优化
|
// 按钮样式优化
|
||||||
:deep(.el-button) {
|
:deep(.el-button) {
|
||||||
border-radius: 8px;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
border-radius: 8px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -321,24 +384,19 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.el-button--primary {
|
&.el-button--primary {
|
||||||
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-dark-2) 100%);
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--el-color-primary) 0%,
|
||||||
|
var(--el-color-primary-dark-2) 100%
|
||||||
|
);
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(135deg, var(--el-color-primary-light-2) 0%, var(--el-color-primary) 100%);
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
420
src/views/system/codegen/utils/download.ts
Normal file
420
src/views/system/codegen/utils/download.ts
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
/**
|
||||||
|
* 代码下载工具函数
|
||||||
|
* 支持根据代码内容自动识别文件类型和生成文件名
|
||||||
|
*/
|
||||||
|
import JSZip from "jszip";
|
||||||
|
|
||||||
|
// 文件类型映射
|
||||||
|
const FILE_TYPE_PATTERNS = {
|
||||||
|
java: [
|
||||||
|
/public\s+class\s+\w+/,
|
||||||
|
/public\s+interface\s+\w+/,
|
||||||
|
/public\s+enum\s+\w+/,
|
||||||
|
/@Entity/,
|
||||||
|
/@Service/,
|
||||||
|
/@Controller/,
|
||||||
|
/@Repository/,
|
||||||
|
/@Component/,
|
||||||
|
/package\s+[\w.]+;/,
|
||||||
|
/import\s+[\w.]+;/
|
||||||
|
],
|
||||||
|
xml: [
|
||||||
|
/<\?xml/,
|
||||||
|
/<mapper/,
|
||||||
|
/<select/,
|
||||||
|
/<insert/,
|
||||||
|
/<update/,
|
||||||
|
/<delete/,
|
||||||
|
/<configuration>/,
|
||||||
|
/<beans>/
|
||||||
|
],
|
||||||
|
sql: [
|
||||||
|
/CREATE\s+TABLE/i,
|
||||||
|
/ALTER\s+TABLE/i,
|
||||||
|
/INSERT\s+INTO/i,
|
||||||
|
/SELECT\s+.*\s+FROM/i,
|
||||||
|
/UPDATE\s+.*\s+SET/i,
|
||||||
|
/DELETE\s+FROM/i,
|
||||||
|
/DROP\s+TABLE/i
|
||||||
|
],
|
||||||
|
js: [
|
||||||
|
/function\s+\w+/,
|
||||||
|
/const\s+\w+\s*=/,
|
||||||
|
/let\s+\w+\s*=/,
|
||||||
|
/var\s+\w+\s*=/,
|
||||||
|
/export\s+/,
|
||||||
|
/import\s+.*\s+from/,
|
||||||
|
/module\.exports/,
|
||||||
|
/require\(/
|
||||||
|
],
|
||||||
|
ts: [
|
||||||
|
/interface\s+\w+/,
|
||||||
|
/type\s+\w+/,
|
||||||
|
/export\s+interface/,
|
||||||
|
/export\s+type/,
|
||||||
|
/:\s*\w+(\[\])?/,
|
||||||
|
/import\s+.*\s+from\s+['"].*\.ts['"]/
|
||||||
|
],
|
||||||
|
vue: [
|
||||||
|
/<template>/,
|
||||||
|
/<script.*>/,
|
||||||
|
/<style.*>/,
|
||||||
|
/export\s+default\s*{/,
|
||||||
|
/defineComponent/
|
||||||
|
],
|
||||||
|
json: [/^\s*{/, /^\s*\[/, /"[\w-]+"\s*:/],
|
||||||
|
yml: [/^[\w-]+:/, /^\s+-\s/, /^---/],
|
||||||
|
properties: [/^[\w.-]+\s*=/, /^#/, /spring\./, /server\./],
|
||||||
|
html: [/<!DOCTYPE\s+html>/i, /<html/i, /<head>/i, /<body>/i, /<div/i, /<p>/i],
|
||||||
|
css: [
|
||||||
|
/\.[a-zA-Z-]+\s*{/,
|
||||||
|
/#[a-zA-Z-]+\s*{/,
|
||||||
|
/@media/,
|
||||||
|
/@import/,
|
||||||
|
/:\s*[\w#.-]+;/
|
||||||
|
],
|
||||||
|
scss: [/\$[\w-]+:/, /@mixin/, /@include/, /@extend/, /&:/]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据代码内容识别文件类型
|
||||||
|
* @param content 代码内容
|
||||||
|
* @returns 文件扩展名
|
||||||
|
*/
|
||||||
|
export function detectFileType(content: string): string {
|
||||||
|
if (!content || content.trim().length === 0) {
|
||||||
|
return "txt";
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedContent = content.trim();
|
||||||
|
|
||||||
|
// 遍历所有文件类型模式
|
||||||
|
for (const [fileType, patterns] of Object.entries(FILE_TYPE_PATTERNS)) {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (pattern.test(trimmedContent)) {
|
||||||
|
return fileType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认返回txt
|
||||||
|
return "txt";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从代码内容中提取有意义的文件名
|
||||||
|
* @param content 代码内容
|
||||||
|
* @param fileType 文件类型
|
||||||
|
* @returns 文件名(不含扩展名)
|
||||||
|
*/
|
||||||
|
export function extractFileName(content: string, fileType: string): string {
|
||||||
|
if (!content || content.trim().length === 0) {
|
||||||
|
return "untitled";
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedContent = content.trim();
|
||||||
|
|
||||||
|
switch (fileType) {
|
||||||
|
case "java":
|
||||||
|
return extractJavaFileName(trimmedContent);
|
||||||
|
case "xml":
|
||||||
|
return extractXmlFileName(trimmedContent);
|
||||||
|
case "sql":
|
||||||
|
return extractSqlFileName(trimmedContent);
|
||||||
|
case "js":
|
||||||
|
case "ts":
|
||||||
|
return extractJsFileName(trimmedContent);
|
||||||
|
case "vue":
|
||||||
|
return extractVueFileName(trimmedContent);
|
||||||
|
default:
|
||||||
|
return generateGenericFileName(trimmedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取Java文件名
|
||||||
|
*/
|
||||||
|
function extractJavaFileName(content: string): string {
|
||||||
|
// 优先提取类名
|
||||||
|
const classMatch = content.match(/public\s+class\s+(\w+)/);
|
||||||
|
if (classMatch) {
|
||||||
|
return classMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取接口名
|
||||||
|
const interfaceMatch = content.match(/public\s+interface\s+(\w+)/);
|
||||||
|
if (interfaceMatch) {
|
||||||
|
return interfaceMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取枚举名
|
||||||
|
const enumMatch = content.match(/public\s+enum\s+(\w+)/);
|
||||||
|
if (enumMatch) {
|
||||||
|
return enumMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从注解中推断
|
||||||
|
if (content.includes("@Entity")) {
|
||||||
|
const entityMatch = content.match(/@Table\s*\(\s*name\s*=\s*["'](\w+)["']/);
|
||||||
|
if (entityMatch) {
|
||||||
|
return toCamelCase(entityMatch[1]) + "Entity";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.includes("@Service")) {
|
||||||
|
return "Service";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.includes("@Controller")) {
|
||||||
|
return "Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.includes("@Repository")) {
|
||||||
|
return "Repository";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "JavaClass";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取XML文件名
|
||||||
|
*/
|
||||||
|
function extractXmlFileName(content: string): string {
|
||||||
|
// MyBatis Mapper
|
||||||
|
const mapperMatch = content.match(
|
||||||
|
/<mapper\s+namespace\s*=\s*["']([\w.]+)["']/
|
||||||
|
);
|
||||||
|
if (mapperMatch) {
|
||||||
|
const namespace = mapperMatch[1];
|
||||||
|
const parts = namespace.split(".");
|
||||||
|
return parts[parts.length - 1] + "Mapper";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spring配置
|
||||||
|
if (content.includes("<beans")) {
|
||||||
|
return "applicationContext";
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyBatis配置
|
||||||
|
if (content.includes("<configuration>")) {
|
||||||
|
return "mybatis-config";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "config";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取SQL文件名
|
||||||
|
*/
|
||||||
|
function extractSqlFileName(content: string): string {
|
||||||
|
// 提取表名
|
||||||
|
const createTableMatch = content.match(/CREATE\s+TABLE\s+[`'"]*(\w+)[`'"]*/i);
|
||||||
|
if (createTableMatch) {
|
||||||
|
return createTableMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const alterTableMatch = content.match(/ALTER\s+TABLE\s+[`'"]*(\w+)[`'"]*/i);
|
||||||
|
if (alterTableMatch) {
|
||||||
|
return alterTableMatch[1] + "_alter";
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertMatch = content.match(/INSERT\s+INTO\s+[`'"]*(\w+)[`'"]*/i);
|
||||||
|
if (insertMatch) {
|
||||||
|
return insertMatch[1] + "_data";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "database";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取JS/TS文件名
|
||||||
|
*/
|
||||||
|
function extractJsFileName(content: string): string {
|
||||||
|
// 提取导出的类名或函数名
|
||||||
|
const exportClassMatch = content.match(/export\s+class\s+(\w+)/);
|
||||||
|
if (exportClassMatch) {
|
||||||
|
return exportClassMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportFunctionMatch = content.match(/export\s+function\s+(\w+)/);
|
||||||
|
if (exportFunctionMatch) {
|
||||||
|
return exportFunctionMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportConstMatch = content.match(/export\s+const\s+(\w+)/);
|
||||||
|
if (exportConstMatch) {
|
||||||
|
return exportConstMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取接口或类型名
|
||||||
|
const interfaceMatch = content.match(/interface\s+(\w+)/);
|
||||||
|
if (interfaceMatch) {
|
||||||
|
return interfaceMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeMatch = content.match(/type\s+(\w+)/);
|
||||||
|
if (typeMatch) {
|
||||||
|
return typeMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "script";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取Vue文件名
|
||||||
|
*/
|
||||||
|
function extractVueFileName(content: string): string {
|
||||||
|
// 从defineOptions中提取name
|
||||||
|
const defineOptionsMatch = content.match(
|
||||||
|
/defineOptions\s*\(\s*{\s*name:\s*["'](\w+)["']/
|
||||||
|
);
|
||||||
|
if (defineOptionsMatch) {
|
||||||
|
return defineOptionsMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从export default中的name属性提取
|
||||||
|
const exportNameMatch = content.match(
|
||||||
|
/export\s+default\s*{\s*name:\s*["'](\w+)["']/
|
||||||
|
);
|
||||||
|
if (exportNameMatch) {
|
||||||
|
return exportNameMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Component";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成通用文件名
|
||||||
|
*/
|
||||||
|
function generateGenericFileName(content: string): string {
|
||||||
|
// 提取第一行作为文件名(去除特殊字符)
|
||||||
|
const firstLine = content.split("\n")[0].trim();
|
||||||
|
if (firstLine.length > 0) {
|
||||||
|
const cleanName = firstLine
|
||||||
|
.replace(/[^\w\s-]/g, "")
|
||||||
|
.replace(/\s+/g, "_")
|
||||||
|
.substring(0, 20);
|
||||||
|
if (cleanName.length > 0) {
|
||||||
|
return cleanName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "untitled";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为驼峰命名
|
||||||
|
*/
|
||||||
|
function toCamelCase(str: string): string {
|
||||||
|
return str
|
||||||
|
.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
|
||||||
|
.replace(/^[a-z]/, letter => letter.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载单个文件
|
||||||
|
* @param content 文件内容
|
||||||
|
* @param originalFileName 原始文件名(可选)
|
||||||
|
*/
|
||||||
|
export function downloadSingleFile(
|
||||||
|
content: string,
|
||||||
|
originalFileName?: string
|
||||||
|
): void {
|
||||||
|
if (!content || content.trim().length === 0) {
|
||||||
|
console.warn("文件内容为空,无法下载");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = originalFileName;
|
||||||
|
|
||||||
|
if (!fileName) {
|
||||||
|
const fileType = detectFileType(content);
|
||||||
|
const baseName = extractFileName(content, fileType);
|
||||||
|
fileName = `${baseName}.${fileType}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建Blob对象
|
||||||
|
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileName;
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量下载文件(需要jszip库支持)
|
||||||
|
* @param files 文件映射 { fileName: content }
|
||||||
|
* @param zipFileName zip文件名
|
||||||
|
*/
|
||||||
|
export async function downloadMultipleFiles(
|
||||||
|
files: Record<string, string>,
|
||||||
|
zipFileName = "generated-code.zip"
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 创建JSZip实例
|
||||||
|
const zip = new JSZip();
|
||||||
|
|
||||||
|
// 添加文件到zip
|
||||||
|
Object.entries(files).forEach(([_templateName, content]) => {
|
||||||
|
if (content && content.trim().length > 0) {
|
||||||
|
const fileType = detectFileType(content);
|
||||||
|
const baseName = extractFileName(content, fileType);
|
||||||
|
const fileName = `${baseName}.${fileType}`;
|
||||||
|
|
||||||
|
zip.file(fileName, content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成zip文件
|
||||||
|
const zipContent = await zip.generateAsync({ type: "blob" });
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const url = URL.createObjectURL(zipContent);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = zipFileName;
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("批量下载失败:", error);
|
||||||
|
// 如果jszip不可用,回退到单个文件下载
|
||||||
|
console.warn("JSZip不可用,回退到单个文件下载");
|
||||||
|
Object.entries(files).forEach(([_templateName, content]) => {
|
||||||
|
if (content && content.trim().length > 0) {
|
||||||
|
downloadSingleFile(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间戳作为文件名后缀
|
||||||
|
*/
|
||||||
|
export function getTimestamp(): string {
|
||||||
|
const now = new Date();
|
||||||
|
return (
|
||||||
|
now.getFullYear().toString() +
|
||||||
|
(now.getMonth() + 1).toString().padStart(2, "0") +
|
||||||
|
now.getDate().toString().padStart(2, "0") +
|
||||||
|
"_" +
|
||||||
|
now.getHours().toString().padStart(2, "0") +
|
||||||
|
now.getMinutes().toString().padStart(2, "0") +
|
||||||
|
now.getSeconds().toString().padStart(2, "0")
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -7,6 +7,11 @@ import {
|
|||||||
TemplateInfo,
|
TemplateInfo,
|
||||||
CodegenRequest
|
CodegenRequest
|
||||||
} from "@/api/system/codegen";
|
} from "@/api/system/codegen";
|
||||||
|
import {
|
||||||
|
downloadSingleFile,
|
||||||
|
downloadMultipleFiles,
|
||||||
|
getTimestamp
|
||||||
|
} from "./download";
|
||||||
|
|
||||||
export function useCodegen() {
|
export function useCodegen() {
|
||||||
// 表单数据
|
// 表单数据
|
||||||
@ -148,6 +153,41 @@ export function useCodegen() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 下载当前代码文件
|
||||||
|
function downloadCurrentCode() {
|
||||||
|
if (!outputStr.value) {
|
||||||
|
ElMessage.warning("没有可下载的内容");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
downloadSingleFile(outputStr.value.trim());
|
||||||
|
ElMessage.success("下载成功");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("下载失败:", error);
|
||||||
|
ElMessage.error("下载失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量下载所有生成的代码文件
|
||||||
|
async function downloadAllCode() {
|
||||||
|
if (!outputJson.value || Object.keys(outputJson.value).length === 0) {
|
||||||
|
ElMessage.warning("没有可下载的内容");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const timestamp = getTimestamp();
|
||||||
|
const zipFileName = `generated-code-${timestamp}.zip`;
|
||||||
|
|
||||||
|
await downloadMultipleFiles(outputJson.value, zipFileName);
|
||||||
|
ElMessage.success("批量下载成功");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("批量下载失败:", error);
|
||||||
|
ElMessage.error("批量下载失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置历史记录
|
// 设置历史记录
|
||||||
function setHistoricalData(tableName: string) {
|
function setHistoricalData(tableName: string) {
|
||||||
// 添加新表名(如果不存在)
|
// 添加新表名(如果不存在)
|
||||||
@ -215,6 +255,8 @@ export function useCodegen() {
|
|||||||
setOutputModel,
|
setOutputModel,
|
||||||
generateCode,
|
generateCode,
|
||||||
copyCode,
|
copyCode,
|
||||||
|
downloadCurrentCode,
|
||||||
|
downloadAllCode,
|
||||||
switchHistoricalData
|
switchHistoricalData
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user