主题和样式以及动画过渡的首页修改

This commit is contained in:
2026-08-14 15:35:03 +08:00
parent 4ec05b507a
commit 362cb4332f
6 changed files with 382 additions and 153 deletions
+39 -23
View File
@@ -150,8 +150,7 @@ watch(
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 16px;
padding: 28px 0 10px;
padding: 8px 0 16px;
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
@@ -167,10 +166,18 @@ watch(
}
}
/* 节奏化消息间距:AI↔AI 24,用户↔AI 16 */
.message-row + .message-row {
margin-top: 16px;
}
.message-row:not(.is-user) + .message-row:not(.is-user) {
margin-top: 24px;
}
.bubble-wrap {
display: flex;
flex-direction: column;
gap: 5px;
gap: 4px;
max-width: 100%;
align-items: flex-start;
@@ -183,7 +190,7 @@ watch(
.bubble {
font-size: 15px;
line-height: 1.75;
line-height: 1.7;
padding: 0;
border-radius: 0;
word-break: break-word;
@@ -192,12 +199,18 @@ watch(
border: none;
box-shadow: none;
/* AI 消息:无背景卡片,纯文本左对齐(DeepSeek 式) */
.message-row:not(.is-user) & {
width: 100%;
max-width: 880px;
}
.message-row.is-user & {
width: fit-content;
max-width: 85%;
padding: 10px 14px;
border-radius: 16px 16px 4px 16px;
background: #d3f5d8;
max-width: 72%;
padding: 10px 16px;
border-radius: 18px 18px 6px 18px;
background: #e8ecff;
color: #1f2328;
box-shadow: none;
}
@@ -223,17 +236,17 @@ watch(
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
width: 30px;
height: 30px;
border: none;
border-radius: 6px;
border-radius: 8px;
background: transparent;
color: #9ca3af;
cursor: pointer;
transition: background 0.15s, color 0.15s;
&:hover {
background: #f1f2f4;
background: #eef0f3;
color: #4b5563;
}
@@ -247,10 +260,10 @@ watch(
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 10px;
padding: 5px 12px;
margin-top: 8px;
padding: 7px 14px;
border: 1px solid #fecaca;
border-radius: 8px;
border-radius: 10px;
background: #fef2f2;
color: #dc2626;
font-size: 12px;
@@ -271,10 +284,12 @@ watch(
/* 思考区:DeepSeek 风格折叠块 */
.thinking-block {
margin: 2px 0 10px;
background: #f5f5f6;
border-radius: 10px;
margin: 0 0 12px;
background: #f5f6f7;
border: 1px solid #eef0f3;
border-radius: 12px;
overflow: hidden;
max-width: 880px;
}
.thinking-toggle {
@@ -282,7 +297,7 @@ watch(
align-items: center;
gap: 6px;
width: 100%;
padding: 8px 12px;
padding: 9px 14px;
border: none;
background: transparent;
font-size: 13px;
@@ -321,7 +336,7 @@ watch(
}
.thinking-content {
padding: 0 12px 12px;
padding: 0 14px 14px;
animation: thinking-fade-in 0.2s ease;
}
@@ -332,7 +347,7 @@ watch(
font-style: italic;
white-space: pre-wrap;
word-break: break-word;
border-top: 1px solid #ececee;
border-top: 1px solid #eef0f3;
padding-top: 10px;
}
@@ -350,9 +365,10 @@ watch(
/* 回答区:Markdown 渲染(DeepSeek 风格排版) */
.answer-block {
white-space: normal;
line-height: 1.75;
line-height: 1.7;
font-size: 15px;
word-break: break-word;
max-width: 880px;
& > :first-child {
margin-top: 0;
@@ -550,7 +566,7 @@ watch(
.time {
font-size: 11px;
color: #8f9aae;
padding: 4px 6px;
padding: 4px 2px;
.message-row.is-user & {
text-align: right;
+77 -36
View File
@@ -2,13 +2,15 @@
<div class="input-shell">
<div class="input-card" :class="{ 'is-focused': isFocused }">
<!-- 已选中状态标签栏 -->
<div v-if="selectedWorkflowId !== null" class="selected-tags">
<div class="selected-tag workflow-tag">
<el-icon><Promotion /></el-icon>
<span>{{ commonWorkflows.find((w) => w.id === selectedWorkflowId)?.name }}</span>
<el-icon v-if="!workflowLocked" class="tag-close" @click="clearWorkflow"><Close /></el-icon>
<Transition name="capsule-slide" @enter="onCapsuleEnter" @after-enter="onCapsuleAfterEnter" @leave="onCapsuleLeave" @after-leave="onCapsuleAfterLeave">
<div v-if="selectedWorkflowId !== null" class="selected-tags">
<div class="selected-tag workflow-tag">
<el-icon><Promotion /></el-icon>
<span>{{ commonWorkflows.find((w) => w.id === selectedWorkflowId)?.name }}</span>
<el-icon v-if="!workflowLocked" class="tag-close" @click="clearWorkflow"><Close /></el-icon>
</div>
</div>
</div>
</Transition>
<!-- 文本输入区 -->
<el-input
@@ -47,19 +49,21 @@
</div>
<!-- 快捷工作流胶囊输入框下方 -->
<div v-if="visibleWorkflows.length > 0" class="workflow-shortcuts">
<button
v-for="wf in visibleWorkflows"
:key="wf.id"
class="shortcut-pill"
:class="{ active: selectedWorkflowId === wf.id }"
@click="toggleWorkflow(wf.id)"
>
<el-icon><Promotion /></el-icon>
{{ wf.name }}
<span v-if="wf.isTemplate" class="pill-tag">模板</span>
</button>
</div>
<Transition name="capsule-slide" @enter="onCapsuleEnter" @after-enter="onCapsuleAfterEnter" @leave="onCapsuleLeave" @after-leave="onCapsuleAfterLeave">
<div v-if="visibleWorkflows.length > 0 && !hideShortcuts" class="workflow-shortcuts">
<button
v-for="wf in visibleWorkflows"
:key="wf.id"
class="shortcut-pill"
:class="{ active: selectedWorkflowId === wf.id }"
@click="toggleWorkflow(wf.id)"
>
<el-icon><Promotion /></el-icon>
{{ wf.name }}
<span v-if="wf.isTemplate" class="pill-tag">模板</span>
</button>
</div>
</Transition>
</div>
</template>
@@ -72,6 +76,7 @@ import { getWorkflowList } from '/@/api/settings/creation';
interface Props {
sendDisabled?: boolean;
workflowLocked?: boolean;
hideShortcuts?: boolean;
}
interface Emits {
@@ -90,6 +95,7 @@ interface Workflow {
const props = withDefaults(defineProps<Props>(), {
sendDisabled: false,
workflowLocked: false,
hideShortcuts: false,
});
const emit = defineEmits<Emits>();
const message = ref('');
@@ -167,21 +173,56 @@ onMounted(() => {
fetchWorkflows();
});
// —— 胶囊高度动画:JS 测量实际高度,让 max-height 与内容高度精确匹配,
// 进入时从 0 展开、离开时收缩到 0,配合 CSS transition 消除底部高度突变 ——
const onCapsuleEnter = (el: Element) => {
const e = el as HTMLElement;
e.style.maxHeight = '0px';
requestAnimationFrame(() => {
e.style.maxHeight = e.scrollHeight + 'px';
});
};
const onCapsuleAfterEnter = (el: Element) => {
(el as HTMLElement).style.maxHeight = '';
};
const onCapsuleLeave = (el: Element) => {
const e = el as HTMLElement;
e.style.maxHeight = e.scrollHeight + 'px';
requestAnimationFrame(() => {
e.style.maxHeight = '0px';
});
};
const onCapsuleAfterLeave = (el: Element) => {
(el as HTMLElement).style.maxHeight = '';
};
defineExpose({ resetAll, clearWorkflow, selectWorkflow, fetchWorkflows, selectedWorkflowId, commonWorkflows });
</script>
<style scoped lang="scss">
/* ===== 胶囊显示/隐藏过渡:淡入淡出 + 轻位移 + 高度展开/收缩 ===== */
.capsule-slide-enter-active,
.capsule-slide-leave-active {
transition: opacity 0.25s ease, transform 0.25s ease, max-height 0.25s ease;
overflow: hidden;
}
.capsule-slide-enter-from,
.capsule-slide-leave-to {
opacity: 0;
transform: translateY(-6px);
}
.input-shell {
padding: 0 24px 24px;
padding: 0 20px 20px;
background: transparent;
}
.input-card {
max-width: 800px;
max-width: 880px;
margin: 0 auto;
background: #fff;
border: 1.5px solid #e2e8f0;
border-radius: 16px;
border: 1.5px solid #e5e8ee;
border-radius: 20px;
box-shadow: 0 2px 12px rgba(15, 23, 42, 0.07);
transition:
border-color 0.2s,
@@ -238,7 +279,7 @@ onMounted(() => {
border: none;
box-shadow: none;
resize: none;
padding: 14px 16px 8px;
padding: 16px 18px 10px;
font-size: 15px;
line-height: 1.6;
color: #0f172a;
@@ -257,7 +298,7 @@ onMounted(() => {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 10px 10px 12px;
padding: 8px 12px 12px 14px;
}
.toolbar-left,
@@ -271,10 +312,10 @@ onMounted(() => {
display: inline-flex;
align-items: center;
gap: 4px;
height: 30px;
height: 34px;
padding: 0 10px;
border: none;
border-radius: 8px;
border-radius: 10px;
background: transparent;
color: #64748b;
font-size: 13px;
@@ -315,10 +356,10 @@ onMounted(() => {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
width: 36px;
height: 36px;
border: none;
border-radius: 8px;
border-radius: 10px;
background: #2563eb;
color: #fff;
cursor: pointer;
@@ -347,8 +388,8 @@ onMounted(() => {
/* 快捷工作流胶囊 */
.workflow-shortcuts {
max-width: 800px;
margin: 10px auto 0;
max-width: 880px;
margin: 8px auto 0;
display: flex;
flex-wrap: wrap;
gap: 8px;
@@ -358,13 +399,13 @@ onMounted(() => {
display: inline-flex;
align-items: center;
gap: 5px;
height: 28px;
height: 32px;
padding: 0 12px;
border: 1px solid #e2e8f0;
border: 1px solid #e5e8ee;
border-radius: 999px;
background: rgba(255, 255, 255, 0.7);
color: #475569;
font-size: 12px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
backdrop-filter: blur(4px);
@@ -372,7 +413,7 @@ onMounted(() => {
outline: none;
.el-icon {
font-size: 12px;
font-size: 13px;
}
&:hover {
+45 -21
View File
@@ -1,7 +1,8 @@
<template>
<div class="main-content">
<Transition name="content-fade" mode="out-in">
<!-- 工作流动态表单 -->
<div v-if="workflowDetail" class="content-body workflow-form-body">
<div v-if="workflowDetail" :key="'workflow'" class="content-body workflow-form-body">
<div class="workflow-form-card">
<div class="workflow-form-header">
<div>
@@ -130,12 +131,12 @@
</div>
<!-- 对话页 — 无工作流但有消息时展示消息流 -->
<div v-else-if="hasMessages" class="content-body chat-body">
<div v-else-if="hasMessages" :key="'chat'" class="content-body chat-body">
<ChatList :messages="messages" @retry="emit('retry', $event)" />
</div>
<!-- 默认占位 — 无工作流时显示引导 -->
<div v-else class="content-body placeholder-body">
<div v-else :key="'placeholder'" class="content-body placeholder-body">
<div class="placeholder-content">
<h2 class="placeholder-title">你好,我能帮你解决什么问题?</h2>
<p class="placeholder-desc">选择一个工作流,填写参数后一键生成</p>
@@ -156,6 +157,7 @@
<div v-else class="placeholder-empty">暂无工作流,可在工作流管理中创建</div>
</div>
</div>
</Transition>
</div>
</template>
@@ -429,7 +431,22 @@ onMounted(() => {
defineExpose({ formValues, fieldFiles, templates, validateFormFields });
</script>
<style scoped lang="scss">.main-content {
<style scoped lang="scss">
/* ===== 页面切换过渡:淡入淡出 + 轻微上移 ===== */
.content-fade-enter-active,
.content-fade-leave-active {
transition: opacity 0.25s ease, transform 0.25s ease;
}
.content-fade-enter-from {
opacity: 0;
transform: translateY(8px);
}
.content-fade-leave-to {
opacity: 0;
transform: translateY(-8px);
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
@@ -446,19 +463,19 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
/* ===== 工作流表单 ===== */
.workflow-form-body {
width: min(800px, 100%);
width: min(880px, calc(100% - 40px));
margin: 0 auto;
height: 100%;
display: flex;
flex-direction: column;
padding: 16px 20px;
padding: 16px 0;
box-sizing: border-box;
}
.workflow-form-card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12px;
border: 1px solid #e5e8ee;
border-radius: 14px;
display: flex;
flex-direction: column;
max-height: 100%;
@@ -569,12 +586,12 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
/* ===== 对话页 ===== */
.chat-body {
width: min(800px, 92%);
width: min(880px, calc(100% - 40px));
margin: 0 auto;
height: 100%;
display: flex;
flex-direction: column;
padding: 0 16px;
padding: 0;
box-sizing: border-box;
overflow-y: auto;
scrollbar-width: none;
@@ -582,16 +599,16 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
}
.placeholder-body { display: flex; align-items: center; justify-content: center; }
.placeholder-content { text-align: center; padding: 24px 20px; width: 100%; max-width: 640px; }
.placeholder-content { text-align: center; padding: 24px 20px; width: 100%; max-width: 740px; }
/* ===== 标题与描述 ===== */
.placeholder-title { font-size: 20px; font-weight: 700; color: #0f172a; margin: 0 0 8px; letter-spacing: -0.2px; line-height: 1.4; }
.placeholder-desc { font-size: 14px; color: #94a3b8; margin: 0 0 28px; line-height: 1.5; }
.placeholder-title { font-size: 26px; font-weight: 700; color: #0f172a; margin: 0 0 8px; letter-spacing: -0.2px; line-height: 1.4; }
.placeholder-desc { font-size: 15px; color: #94a3b8; margin: 0 0 24px; line-height: 1.5; }
/* ===== 工作流功能卡片(DeepSeek 式引导) ===== */
.workflow-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
text-align: left;
}
@@ -599,9 +616,9 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
display: flex;
align-items: center;
gap: 10px;
padding: 14px 16px;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 18px 20px;
border: 1px solid #e5e8ee;
border-radius: 14px;
background: #fff;
cursor: pointer;
transition: all 0.15s;
@@ -613,13 +630,20 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
}
}
.wf-card-icon {
font-size: 18px;
color: #3b82f6;
width: 40px;
height: 40px;
border-radius: 12px;
background: #eaf1ff;
color: #2563eb;
font-size: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.wf-card-name {
flex: 1;
font-size: 14px;
font-size: 15px;
font-weight: 600;
color: #1e293b;
white-space: nowrap;
@@ -638,6 +662,6 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
.placeholder-empty {
font-size: 13px;
color: #cbd5e1;
padding: 24px 0;
padding: 16px 0;
}
</style>
+48 -44
View File
@@ -139,8 +139,8 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
.sidebar {
width: 240px;
flex-shrink: 0;
background: #f8fafc;
border-right: 1px solid #e2e8f0;
background: #f7f8fa;
border-right: 1px solid #e5e8ee;
display: flex;
flex-direction: column;
height: 100%;
@@ -152,20 +152,27 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 12px 12px;
border-bottom: 1px solid #e2e8f0;
padding: 16px 14px 14px;
flex-shrink: 0;
}
.brand-logo {
display: flex;
align-items: center;
gap: 8px;
gap: 9px;
}
.brand-icon {
font-size: 20px;
color: #2563eb;
width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9px;
background: linear-gradient(135deg, #2563eb, #4f8bff);
color: #fff;
font-size: 16px;
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.25);
}
.brand-name {
@@ -194,18 +201,17 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
}
&:hover {
background: #e2e8f0;
background: #e8ebf0;
color: #0f172a;
}
}
/* Tab 切换 */
/* Tab 切换(圆角胶囊) */
.sidebar-tabs {
display: flex;
padding: 8px 8px 0;
padding: 12px 12px 8px;
gap: 4px;
flex-shrink: 0;
border-bottom: 1px solid #e2e8f0;
}
.tab-btn {
@@ -213,26 +219,24 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
height: 34px;
gap: 6px;
height: 36px;
border: none;
border-radius: 8px 8px 0 0;
border-radius: 10px;
background: transparent;
color: #64748b;
font-size: 12px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, color 0.15s;
outline: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
.el-icon {
font-size: 14px;
font-size: 15px;
}
&:hover:not(.active) {
background: #f1f5f9;
background: #eef0f3;
color: #334155;
}
@@ -240,7 +244,7 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
color: #2563eb;
font-weight: 600;
background: #fff;
border-bottom-color: #2563eb;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.06);
}
}
@@ -248,7 +252,7 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
.panel {
flex: 1;
overflow-y: auto;
padding: 8px 8px 12px;
padding: 4px 8px 12px;
scrollbar-width: none;
&::-webkit-scrollbar {
@@ -273,14 +277,14 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 8px;
padding: 10px;
border-radius: 10px;
cursor: pointer;
transition: background 0.15s;
margin-bottom: 1px;
margin-bottom: 2px;
&:hover {
background: #f1f5f9;
background: #eef0f3;
.item-del-btn {
opacity: 1;
@@ -288,7 +292,7 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
}
&.active {
background: #eff6ff;
background: #e6f0ff;
.item-icon {
color: #2563eb;
@@ -325,17 +329,17 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
.item-time {
font-size: 11px;
color: #94a3b8;
margin-top: 1px;
margin-top: 2px;
}
.item-del-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
width: 24px;
height: 24px;
border: none;
border-radius: 5px;
border-radius: 6px;
background: transparent;
color: #94a3b8;
cursor: pointer;
@@ -365,7 +369,7 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
font-size: 11px;
font-weight: 600;
color: #94a3b8;
padding: 6px 4px 2px;
padding: 8px 6px 4px;
letter-spacing: 0.4px;
text-transform: uppercase;
}
@@ -374,13 +378,13 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
display: flex;
align-items: center;
gap: 6px;
padding: 5px 8px 5px 24px;
border-radius: 6px;
padding: 7px 10px 7px 24px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s;
&:hover {
background: #f1f5f9;
background: #eef0f3;
.file-actions {
opacity: 1;
@@ -412,14 +416,6 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
}
.file-action-btn {
&--del {
color: #ef4444;
&:hover {
background: #fee2e2;
color: #dc2626;
}
}
border: none;
background: transparent;
color: #3b82f6;
@@ -433,7 +429,17 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
&:hover {
background: #dbeafe;
}
&--del {
color: #ef4444;
&:hover {
background: #fee2e2;
color: #dc2626;
}
}
}
.status-badge {
font-size: 11px;
padding: 1px 8px;
@@ -449,8 +455,6 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }