Improve lightweight AI design preview dialog
This commit is contained in:
@@ -10,11 +10,11 @@ import {
|
|||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ElAlert,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElDescriptions,
|
ElDescriptions,
|
||||||
ElDescriptionsItem,
|
ElDescriptionsItem,
|
||||||
ElDialog,
|
ElDialog,
|
||||||
ElDivider,
|
|
||||||
ElInput,
|
ElInput,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
ElScrollbar,
|
ElScrollbar,
|
||||||
@@ -39,6 +39,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const draftText = ref('{}');
|
const draftText = ref('{}');
|
||||||
|
const contentText = ref('');
|
||||||
|
|
||||||
const sourceData = computed(() => {
|
const sourceData = computed(() => {
|
||||||
return (
|
return (
|
||||||
@@ -52,7 +53,16 @@ const sourceData = computed(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const payload = computed(() => {
|
const sourceType = computed(() => {
|
||||||
|
const source = sourceData.value as Record<string, any>;
|
||||||
|
if (source.type) return String(source.type);
|
||||||
|
if (props.settings) return 'app_settings';
|
||||||
|
if (props.publishData) return 'dashboard_publish';
|
||||||
|
if (props.basicInfo) return 'dashboard_basic_info';
|
||||||
|
return 'design';
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = computed<Record<string, any>>(() => {
|
||||||
const source = sourceData.value as Record<string, any>;
|
const source = sourceData.value as Record<string, any>;
|
||||||
if ('data' in source && source.data && typeof source.data === 'object') {
|
if ('data' in source && source.data && typeof source.data === 'object') {
|
||||||
return source.data;
|
return source.data;
|
||||||
@@ -60,30 +70,80 @@ const payload = computed(() => {
|
|||||||
return source;
|
return source;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const typeLabelMap: Record<string, string> = {
|
||||||
|
app_design: '应用设计方案',
|
||||||
|
app_settings: '应用设置方案',
|
||||||
|
dashboard_basic_info: '仪表盘基础信息',
|
||||||
|
dashboard_design: '仪表盘设计方案',
|
||||||
|
dashboard_publish: '仪表盘发布配置',
|
||||||
|
database_design: '数据库设计方案',
|
||||||
|
form_basic_info: '表单基础信息',
|
||||||
|
form_ui_design: '表单界面设计',
|
||||||
|
list_config: '列表配置方案',
|
||||||
|
system_summary: '系统生成总结',
|
||||||
|
};
|
||||||
|
|
||||||
const dialogTitle = computed(() => {
|
const dialogTitle = computed(() => {
|
||||||
const source = sourceData.value as Record<string, any>;
|
const source = sourceData.value as Record<string, any>;
|
||||||
return source.title || props.title || 'AI 方案确认';
|
return (
|
||||||
|
source.title ||
|
||||||
|
props.title ||
|
||||||
|
typeLabelMap[sourceType.value] ||
|
||||||
|
'AI 方案确认'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const primaryText = computed(() => {
|
||||||
|
const source = payload.value;
|
||||||
|
return (
|
||||||
|
source.content ||
|
||||||
|
source.design_suggestion ||
|
||||||
|
source.summary ||
|
||||||
|
source.description ||
|
||||||
|
''
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const summaryItems = computed(() => {
|
const summaryItems = computed(() => {
|
||||||
const source = payload.value as Record<string, any>;
|
const source = payload.value;
|
||||||
const keys = ['name', 'title', 'description', 'code', 'type', 'category'];
|
const candidates: Array<[string, any]> = [
|
||||||
return keys
|
['名称', source.name || source.title || source.design_title],
|
||||||
.filter((key) => source[key] !== undefined && source[key] !== null)
|
['编码', source.code || source.dashboard_code || source.form_code],
|
||||||
.map((key) => ({ key, value: String(source[key]) }));
|
['类型', source.type || source.category],
|
||||||
|
['路由', source.route_path || source.path],
|
||||||
|
['菜单', source.menu_name],
|
||||||
|
['图标', source.menu_icon],
|
||||||
|
['排序', source.menu_order],
|
||||||
|
['说明', source.description],
|
||||||
|
];
|
||||||
|
|
||||||
|
return candidates
|
||||||
|
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
||||||
|
.map(([label, value]) => ({ label, value: String(value) }));
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldItems = computed(() => {
|
const fieldItems = computed(() => {
|
||||||
const source = payload.value as Record<string, any>;
|
const source = payload.value;
|
||||||
const fields =
|
const fields =
|
||||||
source.schema_fields || source.form_fields || source.fields || source.schema?.fields;
|
source.schema_fields ||
|
||||||
return Array.isArray(fields) ? fields.slice(0, 12) : [];
|
source.form_fields ||
|
||||||
|
source.fields ||
|
||||||
|
source.schema?.fields;
|
||||||
|
return Array.isArray(fields) ? fields.slice(0, 16) : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableItems = computed(() => {
|
const tableItems = computed(() => {
|
||||||
const source = payload.value as Record<string, any>;
|
const source = payload.value;
|
||||||
const tables = source.data_sources || source.table_configs || source.tables;
|
const tables = source.data_sources || source.table_configs || source.tables;
|
||||||
return Array.isArray(tables) ? tables.slice(0, 8) : [];
|
return Array.isArray(tables) ? tables.slice(0, 10) : [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const routePreview = computed(() => {
|
||||||
|
const source = payload.value;
|
||||||
|
if (source.route_path) return source.route_path;
|
||||||
|
if (source.dashboard_code) return `/dashboard/${source.dashboard_code}`;
|
||||||
|
if (source.form_code) return `/form-render/${source.form_code}`;
|
||||||
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -91,11 +151,24 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
if (props.visible) {
|
if (props.visible) {
|
||||||
draftText.value = JSON.stringify(payload.value || {}, null, 2);
|
draftText.value = JSON.stringify(payload.value || {}, null, 2);
|
||||||
|
contentText.value = primaryText.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function mergedDraft() {
|
||||||
|
const data = JSON.parse(draftText.value || '{}');
|
||||||
|
if (
|
||||||
|
contentText.value &&
|
||||||
|
(sourceType.value === 'app_design' || sourceType.value === 'dashboard_design')
|
||||||
|
) {
|
||||||
|
if ('content' in data) data.content = contentText.value;
|
||||||
|
else data.design_suggestion = contentText.value;
|
||||||
|
}
|
||||||
|
return data && typeof data === 'object' ? data : { value: data };
|
||||||
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
emit('close');
|
emit('close');
|
||||||
@@ -103,11 +176,10 @@ function handleClose() {
|
|||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(draftText.value || '{}');
|
emit('confirm', mergedDraft());
|
||||||
emit('confirm', data && typeof data === 'object' ? data : { value: data });
|
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('JSON 格式不正确');
|
ElMessage.error('JSON 格式不正确,请检查后再确认');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +197,7 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
:model-value="visible"
|
:model-value="visible"
|
||||||
:title="dialogTitle"
|
:title="dialogTitle"
|
||||||
width="860px"
|
width="980px"
|
||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
class="ai-design-preview-dialog"
|
class="ai-design-preview-dialog"
|
||||||
@@ -133,22 +205,44 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
>
|
>
|
||||||
<div class="ai-design-preview">
|
<div class="ai-design-preview">
|
||||||
<section class="preview-panel">
|
<section class="preview-panel">
|
||||||
<div class="panel-title">方案概要</div>
|
<div class="panel-heading">
|
||||||
|
<div>
|
||||||
|
<div class="panel-title">{{ typeLabelMap[sourceType] || '方案预览' }}</div>
|
||||||
|
<div class="panel-subtext">确认后将把当前结构化配置回传给工作流继续执行</div>
|
||||||
|
</div>
|
||||||
|
<ElTag effect="dark" type="primary">{{ sourceType }}</ElTag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ElAlert
|
||||||
|
v-if="routePreview"
|
||||||
|
class="mb-3"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template #title>预计访问路径:{{ routePreview }}</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
<ElDescriptions v-if="summaryItems.length" :column="2" border size="small">
|
<ElDescriptions v-if="summaryItems.length" :column="2" border size="small">
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
v-for="item in summaryItems"
|
v-for="item in summaryItems"
|
||||||
:key="item.key"
|
:key="item.label"
|
||||||
:label="item.key"
|
:label="item.label"
|
||||||
>
|
>
|
||||||
{{ item.value }}
|
{{ item.value }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
<div v-else class="empty-hint">当前方案未提供概要字段</div>
|
<div v-else class="empty-hint">当前方案未提供概要字段</div>
|
||||||
|
|
||||||
<ElDivider v-if="fieldItems.length || tableItems.length" />
|
<template v-if="primaryText">
|
||||||
|
<div class="section-title">方案说明</div>
|
||||||
|
<ElScrollbar height="150px" class="text-preview">
|
||||||
|
<pre>{{ primaryText }}</pre>
|
||||||
|
</ElScrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="fieldItems.length">
|
<template v-if="fieldItems.length">
|
||||||
<div class="panel-subtitle">字段设计</div>
|
<div class="section-title">字段设计</div>
|
||||||
<div class="tag-grid">
|
<div class="tag-grid">
|
||||||
<ElTag
|
<ElTag
|
||||||
v-for="(field, index) in fieldItems"
|
v-for="(field, index) in fieldItems"
|
||||||
@@ -161,14 +255,14 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="tableItems.length">
|
<template v-if="tableItems.length">
|
||||||
<div class="panel-subtitle">数据表配置</div>
|
<div class="section-title">数据/页面配置</div>
|
||||||
<div class="table-list">
|
<div class="table-list">
|
||||||
<div
|
<div
|
||||||
v-for="(table, index) in tableItems"
|
v-for="(table, index) in tableItems"
|
||||||
:key="table.id || table.name || index"
|
:key="table.id || table.name || index"
|
||||||
class="table-item"
|
class="table-item"
|
||||||
>
|
>
|
||||||
<span>{{ table.title || table.name || `数据表 ${index + 1}` }}</span>
|
<span>{{ table.title || table.name || `配置 ${index + 1}` }}</span>
|
||||||
<small>{{ table.code || table.table_name || table.type || '' }}</small>
|
<small>{{ table.code || table.table_name || table.type || '' }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,15 +270,25 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="editor-panel">
|
<section class="editor-panel">
|
||||||
|
<div v-if="primaryText" class="content-editor">
|
||||||
|
<div class="panel-title">可编辑方案正文</div>
|
||||||
|
<ElInput
|
||||||
|
v-model="contentText"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 7, maxRows: 10 }"
|
||||||
|
resize="none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="panel-title-row">
|
<div class="panel-title-row">
|
||||||
<span class="panel-title">结构化数据</span>
|
<span class="panel-title">结构化数据</span>
|
||||||
<ElButton size="small" :icon="Copy" @click="copyJson">复制</ElButton>
|
<ElButton size="small" :icon="Copy" @click="copyJson">复制</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<ElScrollbar height="390px">
|
<ElScrollbar height="330px">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="draftText"
|
v-model="draftText"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:autosize="{ minRows: 18, maxRows: 24 }"
|
:autosize="{ minRows: 16, maxRows: 22 }"
|
||||||
resize="none"
|
resize="none"
|
||||||
class="json-editor"
|
class="json-editor"
|
||||||
/>
|
/>
|
||||||
@@ -204,7 +308,7 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.ai-design-preview {
|
.ai-design-preview {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
|
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,23 +321,30 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
background: var(--el-bg-color);
|
background: var(--el-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-title,
|
.panel-heading,
|
||||||
.panel-title-row {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-title-row {
|
.panel-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-subtitle {
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-subtext {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin: 14px 0 8px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 12px 0 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-hint {
|
.empty-hint {
|
||||||
@@ -243,6 +354,31 @@ function handleVisibleChange(visible: boolean) {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-preview {
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--el-fill-color-lighter);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-preview pre {
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-editor {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-editor .panel-title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-grid {
|
.tag-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
Reference in New Issue
Block a user