优化创作模式表单界面
- 将创作表单面板的类名更改为 `creation-main-panel`,提升代码可读性。 - 新增表单折叠功能,允许用户展开或收起表单,改善用户体验。 - 调整表单项的显示逻辑,确保在没有配置时显示占位信息。 - 更新历史对话区域的样式和内容展示,增强界面友好性。
This commit is contained in:
@@ -188,51 +188,40 @@
|
||||
<div class="main">
|
||||
<!-- 创作模式:动态表单 -->
|
||||
<div v-show="isCreationMode" class="creation-mode-container">
|
||||
<div class="panel creation-form-panel">
|
||||
<div class="panel creation-main-panel">
|
||||
<div class="creation-header">
|
||||
<div>
|
||||
<div class="title">{{ currentWorkflowForCreation?.flowName || '内容创作' }}</div>
|
||||
<div class="sub">{{ currentWorkflowForCreation?.description || '填写表单参数进行内容创作' }}</div>
|
||||
</div>
|
||||
<div class="creation-header-actions">
|
||||
<!-- <el-button type="warning" size="large" @click="showChatModelSelector = true">
|
||||
<el-icon><Setting /></el-icon>
|
||||
设置对话模型
|
||||
</el-button> -->
|
||||
<el-button @click="creationFormCollapsed = !creationFormCollapsed">
|
||||
{{ creationFormCollapsed ? '展开表单' : '收起表单' }}
|
||||
</el-button>
|
||||
<el-button @click="backToCanvas">返回画布</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="creation-form-scroll">
|
||||
<el-form label-position="top" class="creation-form">
|
||||
<template v-if="currentWorkflowForCreation?.nodeInputParams">
|
||||
<div v-for="node in currentWorkflowForCreation.nodeInputParams" :key="node.id" class="node-form-wrapper">
|
||||
<!-- 跳过开始节点,显示其他所有节点 -->
|
||||
<div v-if="node.nodeCode !== '__start__'" class="node-form-section">
|
||||
<div class="node-form-title">
|
||||
<el-icon class="node-icon"><Document /></el-icon>
|
||||
<span>{{ node.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-grid">
|
||||
<!-- 节点基本信息(始终显示) -->
|
||||
<el-form-item label="节点类型" class="form-item-medium">
|
||||
<el-input :model-value="node.nodeCode" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择模型" class="form-item-medium" v-if="node.modelConfig?.modelName">
|
||||
<el-input :model-value="node.modelConfig.modelName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="技能" class="form-item-medium" v-if="node.skillName">
|
||||
<el-input :model-value="node.skillName" disabled />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 自定义表单字段 -->
|
||||
<template v-if="node.formConfig && node.formConfig.length > 0">
|
||||
<div
|
||||
class="creation-middle"
|
||||
:class="{ 'form-collapsed': creationFormCollapsed }"
|
||||
:style="
|
||||
creationFormCollapsed
|
||||
? undefined
|
||||
: { gridTemplateRows: `${formPanelHeightPercent}% 8px minmax(0, calc(100% - ${formPanelHeightPercent}% - 8px))` }
|
||||
"
|
||||
>
|
||||
<div v-show="!creationFormCollapsed" class="creation-form-panel">
|
||||
<div class="simple-form-scroll">
|
||||
<el-form label-position="top" class="simple-creation-form">
|
||||
<template v-if="currentWorkflowForCreation?.nodeInputParams">
|
||||
<template v-for="node in currentWorkflowForCreation.nodeInputParams" :key="node.id">
|
||||
<template v-if="node.nodeCode !== '__start__' && node.formConfig && node.formConfig.length > 0">
|
||||
<el-form-item
|
||||
v-for="field in node.formConfig"
|
||||
:key="`${node.id}_${field.label}`"
|
||||
:label="field.label"
|
||||
:required="field.required"
|
||||
:class="getFieldClass(field.type)"
|
||||
>
|
||||
<el-input
|
||||
v-if="field.type === 'input'"
|
||||
@@ -252,7 +241,7 @@
|
||||
v-else-if="field.type === 'textarea'"
|
||||
v-model="creationFormValues[`${node.id}_${field.label}`]"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:rows="3"
|
||||
:placeholder="field.required ? '必填' : '选填'"
|
||||
:disabled="isFromWorkspace"
|
||||
show-word-limit
|
||||
@@ -267,70 +256,40 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<el-empty v-else description="暂无表单配置" :image-size="80" />
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他配置字段(排除已在 formConfig 中的字段) -->
|
||||
<template v-if="node.config">
|
||||
<el-form-item
|
||||
v-for="(value, key) in node.config"
|
||||
:key="`${node.id}_${key}`"
|
||||
v-show="
|
||||
!['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'].includes(
|
||||
String(key)
|
||||
) && !(node.formConfig || []).some((f: any) => f.label === key || f.label === node.config.fieldMetadata?.[key]?.label)
|
||||
"
|
||||
:label="node.config.fieldMetadata?.[key]?.label || String(key)"
|
||||
:required="node.config.fieldMetadata?.[key]?.required || false"
|
||||
:class="getFieldClass(node.config.fieldMetadata?.[key]?.type || (typeof value === 'string' ? 'textarea' : 'number'))"
|
||||
>
|
||||
<el-input
|
||||
v-if="typeof value === 'string'"
|
||||
v-model="creationFormValues[`${node.id}_${key}`]"
|
||||
:type="node.config.fieldMetadata?.[key]?.type === 'textarea' || String(value).length > 50 ? 'textarea' : 'text'"
|
||||
:rows="4"
|
||||
:placeholder="node.config.fieldMetadata?.[key]?.required ? '必填' : '选填'"
|
||||
:disabled="isFromWorkspace"
|
||||
clearable
|
||||
:show-word-limit="node.config.fieldMetadata?.[key]?.type === 'textarea'"
|
||||
:maxlength="500"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="typeof value === 'number'"
|
||||
v-model="creationFormValues[`${node.id}_${key}`]"
|
||||
class="w100"
|
||||
:controls="true"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
:disabled="isFromWorkspace"
|
||||
/>
|
||||
<el-switch
|
||||
v-else-if="typeof value === 'boolean'"
|
||||
v-model="creationFormValues[`${node.id}_${key}`]"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
:disabled="isFromWorkspace"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!creationFormCollapsed" class="middle-splitter" @mousedown="handleMiddleSplitterMouseDown">
|
||||
<div class="middle-splitter-line"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel creation-history-panel">
|
||||
<div class="history-header">历史对话</div>
|
||||
<div class="history-list-placeholder">
|
||||
<div class="history-item assistant">
|
||||
<div class="role">助手</div>
|
||||
<div class="bubble">这里展示历史对话内容(样式占位,功能待定)。</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-else description="暂无表单配置" :image-size="100" />
|
||||
</el-form>
|
||||
<div class="history-item user">
|
||||
<div class="role">我</div>
|
||||
<div class="bubble">收起上方表单后,此区域可完整展示历史对话。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 创作输入区域 -->
|
||||
<div class="creation-input-area">
|
||||
<!-- 已选文件列表 -->
|
||||
<div v-if="selectedFiles.length > 0" class="selected-files-top">
|
||||
<el-tag v-for="(file, index) in selectedFiles" :key="index" closable @close="removeFile(index)" type="info" size="small">
|
||||
{{ file.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 输入框容器 -->
|
||||
<div class="chat-input-container">
|
||||
<!-- 左侧工具按钮 -->
|
||||
<div class="input-tools-left">
|
||||
<el-upload :auto-upload="false" :show-file-list="false" :on-change="handleFileSelect" multiple>
|
||||
<el-button text :icon="Paperclip" class="tool-btn" />
|
||||
@@ -338,15 +297,12 @@
|
||||
<el-button text :icon="MagicStick" @click="showCreationSkillSelector = true" class="tool-btn" />
|
||||
</div>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<el-input v-model="userInput" placeholder="说点什么..." class="chat-input" @keydown.enter="sendMessage" />
|
||||
|
||||
<!-- 右侧发送/暂停按钮 -->
|
||||
<el-button v-if="!isCreating" type="primary" :icon="Promotion" @click="sendMessage" class="send-btn" circle />
|
||||
<el-button v-else type="danger" :icon="VideoPause" @click="stopExecution" class="send-btn" circle />
|
||||
</div>
|
||||
|
||||
<!-- 已选技能标签 -->
|
||||
<div v-if="selectedCreationSkill" class="selected-skill-bottom">
|
||||
<el-tag type="success" closable @close="selectedCreationSkill = null" size="small"> 技能: {{ selectedCreationSkill.name }} </el-tag>
|
||||
</div>
|
||||
@@ -683,6 +639,9 @@ const showCreationSkillSelector = ref(false);
|
||||
const currentSessionId = ref<string | null>(null); // 当前会话的 sessionId(从工作空间进入时使用)
|
||||
const isFromWorkspace = ref(false); // 是否从工作空间进入创作模式
|
||||
const isCreating = ref(false);
|
||||
const creationFormCollapsed = ref(false);
|
||||
const formPanelHeightPercent = ref(50);
|
||||
const isDraggingMiddleSplitter = ref(false);
|
||||
// 预览相关状态
|
||||
const previewDialogVisible = ref(false);
|
||||
const previewUrl = ref('');
|
||||
@@ -1447,11 +1406,25 @@ const stopExecution = async () => {
|
||||
ElMessage.error('终止执行失败');
|
||||
}
|
||||
};
|
||||
// 判断节点是否有可见字段
|
||||
const _hasVisibleFields = (node: any) => {
|
||||
if (!node.config) return false;
|
||||
const excludeKeys = ['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'];
|
||||
return Object.keys(node.config).some((key) => !excludeKeys.includes(key));
|
||||
|
||||
const handleMiddleSplitterMouseDown = () => {
|
||||
if (creationFormCollapsed.value) return;
|
||||
isDraggingMiddleSplitter.value = true;
|
||||
};
|
||||
|
||||
const handleGlobalMouseMove = (e: MouseEvent) => {
|
||||
if (!isDraggingMiddleSplitter.value) return;
|
||||
const middleEl = document.querySelector('.creation-middle') as HTMLElement | null;
|
||||
if (!middleEl) return;
|
||||
|
||||
const rect = middleEl.getBoundingClientRect();
|
||||
const y = e.clientY - rect.top;
|
||||
const ratio = (y / rect.height) * 100;
|
||||
formPanelHeightPercent.value = Math.min(75, Math.max(25, ratio));
|
||||
};
|
||||
|
||||
const handleGlobalMouseUp = () => {
|
||||
isDraggingMiddleSplitter.value = false;
|
||||
};
|
||||
// 根据字段类型返回CSS类名
|
||||
const getFieldClass = (type: string) => {
|
||||
@@ -2520,6 +2493,8 @@ onMounted(async () => {
|
||||
initLogicFlow();
|
||||
await getNodeLibrary();
|
||||
await fetchWorkflowList();
|
||||
window.addEventListener('mousemove', handleGlobalMouseMove);
|
||||
window.addEventListener('mouseup', handleGlobalMouseUp);
|
||||
|
||||
// 获取当前用户角色
|
||||
try {
|
||||
@@ -2530,6 +2505,8 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('mousemove', handleGlobalMouseMove);
|
||||
window.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||
logicFlowInstance.value?.destroy();
|
||||
logicFlowInstance.value = null;
|
||||
});
|
||||
@@ -2672,20 +2649,74 @@ onBeforeUnmount(() => {
|
||||
display: block;
|
||||
}
|
||||
.creation-mode-container {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.creation-form-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.creation-form-panel.collapsed {
|
||||
display: none;
|
||||
}
|
||||
.creation-history-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.history-header {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.history-list-placeholder {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.history-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.history-item .role {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.history-item .bubble {
|
||||
max-width: 86%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
}
|
||||
.history-item.assistant .bubble {
|
||||
background: #f1f5f9;
|
||||
color: #0f172a;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.history-item.user {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.history-item.user .bubble {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
.creation-form-panel.collapsed + .creation-history-panel {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
/* 画布模式:画布和侧边栏并排 */
|
||||
.panel.canvas-panel {
|
||||
flex: 1;
|
||||
@@ -3040,6 +3071,9 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.creation-middle {
|
||||
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
.workflow-list-panel {
|
||||
padding: 16px;
|
||||
@@ -3170,15 +3204,111 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.creation-main-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.creation-middle {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) 8px minmax(0, 1fr);
|
||||
gap: 0;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.creation-middle.form-collapsed {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.middle-splitter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: row-resize;
|
||||
user-select: none;
|
||||
}
|
||||
.middle-splitter-line {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #dbe4ef;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.middle-splitter:hover .middle-splitter-line {
|
||||
background: #8db4f7;
|
||||
}
|
||||
.creation-form-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
.simple-form-scroll {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
.simple-creation-form :deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.creation-history-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
}
|
||||
.history-header {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.history-list-placeholder {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.history-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.history-item .role {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.history-item .bubble {
|
||||
max-width: 86%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
}
|
||||
.history-item.assistant .bubble {
|
||||
background: #f1f5f9;
|
||||
color: #0f172a;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.history-item.user {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.history-item.user .bubble {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
.creation-header {
|
||||
display: flex;
|
||||
@@ -3387,6 +3517,9 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.creation-middle {
|
||||
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 左侧Tab面板样式 */
|
||||
|
||||
Reference in New Issue
Block a user