feat: 完善 Prompt 管理与生成流程配置

This commit is contained in:
王鹏
2026-07-15 17:31:13 +08:00
parent 84d3c8fc05
commit de98ea59b1
93 changed files with 3474 additions and 980 deletions

View File

@@ -0,0 +1,8 @@
{
"hash": "8f672fc6",
"configHash": "35975680",
"lockfileHash": "ed79ecfc",
"browserHash": "7a57d982",
"optimized": {},
"chunks": {}
}

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -63,56 +63,77 @@
/>
<el-collapse v-model="oneClickAdvancedSections" class="advanced-settings">
<el-collapse-item title="高级设置" name="advanced">
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="视觉方案">
<el-radio-group v-model="projectForm.stylePreset" class="style-theme-group">
<el-radio
v-for="preset in availablePageStylePresets"
:key="preset.code"
:label="preset.code"
class="style-theme-option"
>
<span class="style-theme-card" :style="themePreviewStyle(preset)">
<span class="style-theme-preview" :class="solutionPreviewClasses(preset)" :style="themePreviewStyle(preset)" aria-hidden="true">
<span class="theme-preview-bar" />
<span class="theme-preview-body">
<span class="theme-preview-media" />
<span class="theme-preview-lines">
<span />
<span />
<span />
</span>
</span>
<span class="theme-preview-swatches">
<template #title>
<div class="advanced-settings-title">
<span class="advanced-settings-title-icon" aria-hidden="true" />
<span class="advanced-settings-title-copy">
<strong>高级设置</strong>
<small>视觉风格生成范围与代码模板</small>
</span>
<span class="advanced-settings-summary">
{{ selectedStylePreset?.label || '未选择视觉方案' }}
<i />
{{ projectForm.frontendEnabled ? '包含用户前台' : '仅后台管理' }}
</span>
</div>
</template>
<div class="advanced-settings-body">
<el-form-item label="视觉方案" class="advanced-setting-section visual-theme-setting">
<p class="advanced-setting-help">统一前台与后台的导航色彩和内容布局</p>
<el-radio-group v-model="projectForm.stylePreset" class="style-theme-group">
<el-radio
v-for="preset in availablePageStylePresets"
:key="preset.code"
:label="preset.code"
class="style-theme-option"
>
<span class="style-theme-card" :style="themePreviewStyle(preset)">
<span class="style-theme-preview" :class="solutionPreviewClasses(preset)" :style="themePreviewStyle(preset)" aria-hidden="true">
<span class="theme-preview-bar" />
<span class="theme-preview-body">
<span class="theme-preview-media" />
<span class="theme-preview-lines">
<span />
<span />
<span />
</span>
</span>
<span class="style-theme-copy">
<strong>{{ preset.label }}</strong>
<small>{{ preset.description }}</small>
<span class="style-solution-tags">
<em v-for="tag in solutionMetaLabels(preset)" :key="tag">{{ tag }}</em>
</span>
<span class="theme-preview-swatches">
<span />
<span />
<span />
</span>
</span>
</el-radio>
</el-radio-group>
<span class="style-theme-copy">
<strong>{{ preset.label }}</strong>
<small>{{ preset.description }}</small>
<span class="style-solution-tags">
<em v-for="tag in solutionMetaLabels(preset)" :key="tag">{{ tag }}</em>
</span>
</span>
</span>
</el-radio>
</el-radio-group>
</el-form-item>
<div class="advanced-secondary-grid">
<el-form-item label="用户前台" class="advanced-setting-section frontend-setting-section">
<p class="advanced-setting-help">选择完整双端项目或只保留后台管理端</p>
<div class="frontend-setting-control">
<span class="frontend-setting-copy">
<strong>{{ projectForm.frontendEnabled ? '生成用户前台' : '仅后台管理' }}</strong>
<small>{{ projectForm.frontendEnabled ? '生成面向用户的页面与菜单' : '不生成用户侧页面和菜单' }}</small>
</span>
<el-switch
v-model="projectForm.frontendEnabled"
aria-label="生成用户前台"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="用户前台">
<el-switch
v-model="projectForm.frontendEnabled"
active-text="生成用户前台"
inactive-text="仅后台管理"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="代码模板">
<el-form-item label="代码模板" class="advanced-setting-section code-template-setting">
<p class="advanced-setting-help">决定生成项目的技术栈和基础目录结构</p>
<div class="code-template-panel" :style="themePreviewStyle(selectedStylePreset)">
<div class="code-template-cover">
<img
@@ -120,11 +141,16 @@
:src="templatePreviewUrl(selectedCodeTemplateBundle)"
:alt="selectedCodeTemplateBundle.bundleName || selectedCodeTemplateBundle.bundleCode"
/>
<span v-else>未上传展示图</span>
<span v-else class="code-template-placeholder">
<i aria-hidden="true" />
<small>未上传展示图</small>
</span>
</div>
<div class="code-template-control">
<strong>{{ selectedCodeTemplateBundle.bundleName || selectedCodeTemplateBundle.bundleCode }}</strong>
<small>{{ selectedCodeTemplateBundle.description || '按当前模板生成后端、前台、后台和 SQL' }}</small>
<span class="code-template-copy">
<strong>{{ selectedCodeTemplateBundle.bundleName || selectedCodeTemplateBundle.bundleCode }}</strong>
<small>{{ selectedCodeTemplateBundle.description || '按当前模板生成后端、前台、后台和 SQL' }}</small>
</span>
<el-select v-model="projectForm.codeTemplate" class="code-template-select" filterable>
<el-option
v-for="bundle in codeTemplateBundles"
@@ -151,8 +177,8 @@
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
</el-collapse-item>
</el-collapse>
<el-form-item class="one-click-action-item">
@@ -3350,8 +3376,198 @@ onBeforeUnmount(() => {
}
.advanced-settings {
overflow: hidden;
max-width: 1180px;
margin-bottom: 16px;
border: 1px solid #dfe7f1;
border-radius: 12px;
background: #fff;
:deep(.el-collapse-item__header) {
min-height: 64px;
height: auto;
padding: 0 16px;
border-bottom-color: #e7edf5;
background: #fff;
}
:deep(.el-collapse-item__header:not(.is-active)) {
border-bottom-color: transparent;
}
:deep(.el-collapse-item__arrow) {
margin-left: 16px;
color: #64748b;
font-size: 14px;
}
:deep(.el-collapse-item__wrap) {
border-bottom: 0;
}
:deep(.el-collapse-item__content) {
padding-bottom: 0;
color: inherit;
}
}
.advanced-settings-title {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
width: 100%;
padding-right: 4px;
}
.advanced-settings-title-icon {
position: relative;
flex: 0 0 auto;
width: 32px;
height: 32px;
border-radius: 9px;
background: linear-gradient(145deg, #eff6ff, #dbeafe);
&::before,
&::after {
position: absolute;
left: 8px;
width: 16px;
height: 3px;
border-radius: 999px;
background: #3b82f6;
content: '';
}
&::before {
top: 10px;
box-shadow: 0 9px 0 #60a5fa;
}
&::after {
top: 8px;
left: 17px;
width: 4px;
height: 7px;
background: #fff;
box-shadow: -6px 9px 0 #fff;
}
}
.advanced-settings-title-copy {
display: grid;
gap: 3px;
min-width: 0;
strong {
color: #172033;
font-size: 15px;
line-height: 1.25;
}
small {
color: #8793a5;
font-size: 12px;
line-height: 1.25;
}
}
.advanced-settings-summary {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
margin-left: auto;
overflow: hidden;
color: #64748b;
font-size: 12px;
line-height: 1.4;
text-overflow: ellipsis;
white-space: nowrap;
i {
flex: 0 0 auto;
width: 3px;
height: 3px;
border-radius: 50%;
background: #cbd5e1;
}
}
.advanced-settings-body {
display: grid;
gap: 12px;
padding: 16px;
background: #f7f9fc;
}
.advanced-setting-section {
box-sizing: border-box;
height: 100%;
margin-bottom: 0;
padding: 16px;
border: 1px solid #e3eaf3;
border-radius: 10px;
background: #fff;
:deep(.el-form-item__label) {
height: auto;
padding: 0;
color: #172033;
font-size: 14px;
font-weight: 700;
line-height: 1.35;
}
:deep(.el-form-item__content) {
display: block;
min-width: 0;
line-height: normal;
}
}
.advanced-setting-help {
margin: 5px 0 14px;
color: #7a8799;
font-size: 12px;
line-height: 1.5;
}
.advanced-secondary-grid {
display: grid;
grid-template-columns: minmax(280px, 0.72fr) minmax(520px, 1.28fr);
gap: 12px;
align-items: stretch;
}
.frontend-setting-control {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
min-height: 72px;
padding: 14px;
border: 1px solid #e8eef6;
border-radius: 9px;
background: #f8fafc;
}
.frontend-setting-copy {
display: grid;
gap: 5px;
min-width: 0;
strong {
color: #243047;
font-size: 14px;
line-height: 1.35;
}
small {
color: #7a8799;
font-size: 12px;
line-height: 1.45;
}
}
.one-click-primary-actions {
@@ -3439,69 +3655,101 @@ onBeforeUnmount(() => {
.code-template-panel {
display: grid;
grid-template-columns: minmax(190px, 260px) minmax(0, 1fr);
gap: 12px;
grid-template-columns: 156px minmax(0, 1fr);
gap: 14px;
width: 100%;
max-width: 980px;
padding: 10px;
border: 1px solid var(--theme-border);
border-radius: 8px;
background: var(--theme-bg);
min-width: 0;
}
.code-template-cover {
display: grid;
place-items: center;
min-width: 0;
min-height: 132px;
height: 112px;
overflow: hidden;
border: 1px solid var(--theme-border);
border-radius: var(--theme-radius);
background: var(--theme-surface);
box-shadow: var(--theme-shadow);
border-radius: 8px;
background: var(--theme-soft);
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
span {
color: #94a3b8;
font-size: 13px;
.code-template-placeholder {
display: grid;
justify-items: center;
gap: 8px;
color: #94a3b8;
i {
position: relative;
display: block;
width: 38px;
height: 28px;
border: 1px solid var(--theme-border);
border-radius: 6px;
background: var(--theme-surface);
&::before {
position: absolute;
right: 6px;
bottom: 6px;
left: 6px;
height: 8px;
border-radius: 3px;
background: linear-gradient(135deg, transparent 34%, var(--theme-primary) 35% 58%, var(--theme-accent) 59%);
opacity: 0.5;
content: '';
}
}
small {
font-size: 11px;
}
}
.code-template-control {
display: grid;
gap: 8px;
gap: 12px;
min-width: 0;
align-content: space-between;
}
.code-template-copy {
display: grid;
gap: 4px;
min-width: 0;
align-content: center;
strong {
min-width: 0;
overflow: hidden;
color: #172033;
font-size: 14px;
font-size: 15px;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
small {
display: -webkit-box;
overflow: hidden;
color: #667085;
font-size: 12px;
line-height: 1.45;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
.style-theme-group {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
gap: 12px;
align-items: stretch;
width: 100%;
max-width: 1180px;
}
.style-theme-alert {
@@ -3512,7 +3760,7 @@ onBeforeUnmount(() => {
position: relative;
display: flex;
width: 100%;
height: 112px;
min-height: 138px;
margin-right: 0;
white-space: normal;
@@ -3530,20 +3778,38 @@ onBeforeUnmount(() => {
&.is-checked .style-theme-card {
border-color: var(--theme-primary);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.14);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--theme-primary) 15%, transparent);
&::after {
position: absolute;
top: 10px;
right: 10px;
display: grid;
place-items: center;
width: 20px;
height: 20px;
border-radius: 50%;
color: #fff;
background: var(--theme-primary);
font-size: 12px;
font-weight: 700;
content: '✓';
}
}
}
.style-theme-card {
position: relative;
display: grid;
grid-template-columns: 88px minmax(0, 1fr);
gap: 10px;
grid-template-columns: 92px minmax(0, 1fr);
gap: 12px;
align-items: center;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 10px;
padding: 12px;
border: 1px solid #dce5ef;
border-radius: 8px;
border-radius: 10px;
background: #fff;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
@@ -3556,8 +3822,9 @@ onBeforeUnmount(() => {
.style-theme-preview {
display: grid;
gap: 6px;
width: 88px;
height: 68px;
width: 92px;
height: 76px;
box-sizing: border-box;
padding: 8px;
border: 1px solid var(--theme-border);
border-radius: 8px;
@@ -3667,16 +3934,17 @@ onBeforeUnmount(() => {
.style-theme-copy {
display: grid;
grid-template-rows: auto 35px auto;
gap: 4px;
grid-template-rows: auto minmax(34px, auto) auto;
gap: 7px;
min-width: 0;
align-content: center;
strong {
min-width: 0;
padding-right: 22px;
overflow: hidden;
color: #172033;
font-size: 14px;
font-size: 15px;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
@@ -3694,19 +3962,24 @@ onBeforeUnmount(() => {
}
.style-solution-tags {
display: flex;
flex-wrap: wrap;
display: grid;
gap: 4px;
margin-top: 2px;
justify-items: start;
min-width: 0;
em {
box-sizing: border-box;
max-width: 100%;
padding: 2px 6px;
overflow: hidden;
border-radius: 999px;
color: var(--theme-primary);
background: var(--theme-soft);
font-size: 10px;
font-style: normal;
line-height: 1.4;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@@ -4078,11 +4351,25 @@ onBeforeUnmount(() => {
gap: 18px;
}
@media (max-width: 1100px) {
.advanced-secondary-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 860px) {
.code-template-panel {
grid-template-columns: 1fr;
}
.code-template-cover {
height: 150px;
}
.advanced-settings-summary {
display: none;
}
.mode-switcher-panel {
align-items: stretch;
flex-direction: column;
@@ -4111,4 +4398,29 @@ onBeforeUnmount(() => {
flex-direction: column;
}
}
@media (max-width: 560px) {
.advanced-settings-body {
padding: 10px;
}
.advanced-setting-section {
padding: 13px;
}
.advanced-settings-title-copy small {
display: none;
}
.style-theme-card {
grid-template-columns: 82px minmax(0, 1fr);
gap: 10px;
padding: 10px;
}
.style-theme-preview {
width: 82px;
height: 72px;
}
}
</style>

View File

@@ -440,12 +440,15 @@ test('generate page keeps visual themes compact and removes the large impact pre
assert.doesNotMatch(source, /class="style-theme-note"/)
})
test('generate page gives every visual theme card the same dimensions', () => {
test('generate page keeps visual theme cards readable and aligned in the new settings grid', () => {
const source = readSource('views/GenerateView.vue')
assert.match(source, /grid-template-columns: repeat\(auto-fill, minmax\(min\(220px, 100%\), 1fr\)\)/)
assert.match(source, /\.style-theme-option \{[\s\S]*?width: 100%;[\s\S]*?height: 112px;/)
assert.match(source, /class="advanced-settings-body"/)
assert.match(source, /class="advanced-secondary-grid"/)
assert.match(source, /grid-template-columns: repeat\(auto-fill, minmax\(min\(260px, 100%\), 1fr\)\)/)
assert.match(source, /\.style-theme-option \{[\s\S]*?width: 100%;[\s\S]*?min-height: 138px;/)
assert.match(source, /\.style-theme-card \{[\s\S]*?width: 100%;[\s\S]*?height: 100%;/)
assert.match(source, /\.style-solution-tags \{[\s\S]*?display: grid;/)
})
test('generate page displays one-click business loop report', () => {