feat: 统一管理端弹窗与内容区交互样式

- 收敛管理端公共 Modal 链路,新增表单弹窗与普通内容弹窗包装\n- 迁移 Bot、知识库、插件、工作流、资源、MCP、数据中枢与系统管理页面级弹窗\n- 统一内容区工具栏、列表容器、导航与顶部按钮的视觉密度和交互节奏
This commit is contained in:
2026-03-06 19:58:26 +08:00
parent 76c2954a70
commit b191d1aaed
99 changed files with 3148 additions and 1623 deletions

View File

@@ -55,7 +55,11 @@ const style = computed((): CSSProperties => {
</script>
<template>
<main ref="contentElement" :style="style" class="bg-background-deep relative">
<main
ref="contentElement"
:style="style"
class="relative overflow-hidden bg-[radial-gradient(circle_at_top,hsl(var(--glass-tint))/0.34,transparent_40%),linear-gradient(180deg,hsl(var(--surface-canvas)),hsl(var(--background-deep)))]"
>
<Slot :style="overlayStyle">
<slot name="overlay"></slot>
</Slot>

View File

@@ -47,6 +47,11 @@ const style = computed((): CSSProperties => {
const right = !show || !fullWidth ? undefined : 0;
return {
backgroundColor: 'hsl(var(--nav-surface) / 0.86)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-surface) / 0.96), hsl(var(--glass-tint) / 0.82))',
backdropFilter: 'blur(var(--glass-blur)) saturate(160%)',
boxShadow: 'var(--shadow-subtle)',
height: `${height}px`,
marginTop: show ? 0 : `-${height}px`,
right,
@@ -58,20 +63,30 @@ const logoStyle = computed((): CSSProperties => {
minWidth: `${props.isMobile ? 40 : props.sidebarWidth}px`,
};
});
const ambientStyle: CSSProperties = {
backgroundImage:
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.42) 0%, hsl(var(--nav-ambient) / 0.18) 22%, transparent 56%), radial-gradient(circle at 20% 0%, hsl(var(--nav-ambient-secondary) / 0.22) 0%, transparent 40%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.22), transparent 48%)',
};
</script>
<template>
<header
:class="theme"
:style="style"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
class="relative top-0 flex w-full flex-[0_0_auto] items-center gap-1 overflow-x-hidden overflow-y-visible px-3 transition-[margin-top,background-color,box-shadow] duration-200"
>
<div v-if="slots.logo" :style="logoStyle">
<div :style="ambientStyle" class="pointer-events-none absolute inset-0"></div>
<div v-if="slots.logo" :style="logoStyle" class="relative z-10">
<slot name="logo"></slot>
</div>
<slot name="toggle-button"> </slot>
<div class="relative z-10">
<slot name="toggle-button"> </slot>
</div>
<slot></slot>
<div class="relative z-10 flex min-w-0 flex-1 items-center">
<slot></slot>
</div>
</header>
</template>

View File

@@ -90,7 +90,7 @@ interface Props {
}
const props = withDefaults(defineProps<Props>(), {
collapseHeight: 42,
collapseHeight: 96,
collapseWidth: 48,
domVisible: true,
fixedExtra: false,
@@ -122,7 +122,13 @@ const style = computed((): CSSProperties => {
const { isSidebarMixed, marginTop, paddingTop, zIndex } = props;
return {
'--scroll-shadow': 'var(--sidebar)',
'--scroll-shadow': 'var(--surface-elevated)',
backgroundColor: 'hsl(var(--nav-surface-subtle) / 0.44)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-surface) / 0.66) 0%, hsl(var(--nav-surface-subtle) / 0.5) 28%, hsl(var(--glass-tint) / 0.36) 100%)',
backdropFilter: 'blur(calc(var(--glass-blur) * 1.5)) saturate(188%)',
boxShadow:
'0 26px 64px -42px hsl(var(--primary) / 0.28), inset 0 1px 0 hsl(var(--nav-sheen) / 0.42), inset -1px 0 0 hsl(var(--nav-sheen) / 0.14)',
...calcMenuWidthStyle(false),
height: `calc(100% - ${marginTop}px)`,
marginTop: `${marginTop}px`,
@@ -136,17 +142,31 @@ const extraStyle = computed((): CSSProperties => {
const { extraWidth, show, width, zIndex } = props;
return {
backgroundColor: 'hsl(var(--nav-surface-subtle) / 0.4)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-surface-subtle) / 0.62) 0%, hsl(var(--glass-tint) / 0.34) 100%)',
backdropFilter: 'blur(calc(var(--glass-blur) * 1.34)) saturate(182%)',
boxShadow:
'0 26px 64px -42px hsl(var(--primary) / 0.24), inset 0 1px 0 hsl(var(--nav-sheen) / 0.34), inset -1px 0 0 hsl(var(--nav-sheen) / 0.1)',
left: `${width}px`,
width: extraVisible.value && show ? `${extraWidth}px` : 0,
zIndex,
};
});
const shellAmbientStyle: CSSProperties = {
backgroundImage:
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.58) 0%, hsl(var(--nav-ambient) / 0.22) 20%, transparent 56%), radial-gradient(circle at 14% 0%, hsl(var(--nav-ambient-secondary) / 0.24) 0%, transparent 34%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.34), transparent 16%)',
};
const extraAmbientStyle: CSSProperties = {
backgroundImage:
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.42) 0%, hsl(var(--nav-ambient) / 0.16) 18%, transparent 44%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.28), transparent 16%)',
};
const extraTitleStyle = computed((): CSSProperties => {
const { headerHeight } = props;
return {
height: `${headerHeight - 1}px`,
height: `${Math.max(headerHeight - 1, 0)}px`,
};
});
@@ -163,7 +183,7 @@ const contentStyle = computed((): CSSProperties => {
return {
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
paddingTop: '8px',
paddingTop: headerHeight > 0 ? '10px' : '52px',
...contentWidthStyle.value,
};
});
@@ -173,7 +193,7 @@ const headerStyle = computed((): CSSProperties => {
return {
...(isSidebarMixed ? { display: 'flex', justifyContent: 'center' } : {}),
height: `${headerHeight - 1}px`,
height: `${Math.max(headerHeight - 1, 0)}px`,
...contentWidthStyle.value,
};
});
@@ -182,12 +202,13 @@ const extraContentStyle = computed((): CSSProperties => {
const { collapseHeight, headerHeight } = props;
return {
height: `calc(100% - ${headerHeight + collapseHeight}px)`,
paddingTop: headerHeight > 0 ? '10px' : '52px',
};
});
const collapseStyle = computed((): CSSProperties => {
const toolStyle = computed((): CSSProperties => {
return {
height: `${props.collapseHeight}px`,
top: `${Math.max(10, (props.headerHeight - 36) / 2)}px`,
};
});
@@ -263,60 +284,70 @@ function handleMouseleave() {
:class="[
theme,
{
'bg-sidebar-deep': isSidebarMixed,
'bg-sidebar border-border border-r': !isSidebarMixed,
'bg-[hsl(var(--glass-tint))/0.72]': isSidebarMixed,
'bg-[hsl(var(--glass-tint))/0.82]': !isSidebarMixed,
},
]"
:style="style"
class="fixed left-0 top-0 h-full transition-all duration-150"
class="fixed left-0 top-0 h-full overflow-hidden transition-all duration-150"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
>
<SidebarFixedButton
v-if="!collapse && !isSidebarMixed && showFixedButton"
v-model:expand-on-hover="expandOnHover"
/>
<div v-if="slots.logo" :style="headerStyle">
<div :style="shellAmbientStyle" class="pointer-events-none absolute inset-0"></div>
<div
v-if="slots.logo"
:style="headerStyle"
class="relative z-10 px-2 pt-2"
>
<slot name="logo"></slot>
</div>
<EasyFlowScrollbar :style="contentStyle" shadow shadow-border>
<EasyFlowScrollbar :style="contentStyle" class="relative z-10">
<slot></slot>
</EasyFlowScrollbar>
<div :style="collapseStyle"></div>
<SidebarCollapseButton
v-if="showCollapseButton && !isSidebarMixed"
v-model:collapsed="collapse"
/>
<div
v-if="!isSidebarMixed && !collapse && showFixedButton"
:style="toolStyle"
class="absolute right-3 z-20 flex"
>
<SidebarFixedButton
v-model:expand-on-hover="expandOnHover"
/>
</div>
<div
v-if="isSidebarMixed"
ref="asideRef"
:class="{
'border-l': extraVisible,
}"
:style="extraStyle"
class="border-border bg-sidebar fixed top-0 h-full overflow-hidden border-r transition-all duration-200"
class="fixed top-0 h-full overflow-hidden transition-all duration-200"
>
<SidebarCollapseButton
v-if="isSidebarMixed && expandOnHover"
v-model:collapsed="extraCollapse"
/>
<SidebarFixedButton
<div :style="extraAmbientStyle" class="pointer-events-none absolute inset-0"></div>
<div
v-if="!extraCollapse"
v-model:expand-on-hover="expandOnHover"
/>
<div v-if="!extraCollapse" :style="extraTitleStyle" class="pl-2">
:style="extraTitleStyle"
class="relative z-10 px-3"
>
<slot name="extra-title"></slot>
</div>
<EasyFlowScrollbar
:style="extraContentStyle"
class="border-border py-2"
shadow
shadow-border
class="relative z-10 py-2"
>
<slot name="extra"></slot>
</EasyFlowScrollbar>
<div
v-if="expandOnHover || !extraCollapse"
:style="toolStyle"
class="absolute right-3 z-20 flex flex-col gap-2"
>
<SidebarCollapseButton
v-if="expandOnHover"
v-model:collapsed="extraCollapse"
/>
<SidebarFixedButton
v-if="!extraCollapse"
v-model:expand-on-hover="expandOnHover"
/>
</div>
</div>
</aside>
</template>

View File

@@ -15,16 +15,28 @@ const props = withDefaults(defineProps<Props>(), {});
const style = computed((): CSSProperties => {
const { height } = props;
return {
backgroundColor: 'hsl(var(--glass-tint) / 0.68)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-surface-subtle) / 0.92), hsl(var(--glass-tint) / 0.74))',
backdropFilter: 'blur(calc(var(--glass-blur) * 0.9)) saturate(155%)',
boxShadow: '0 18px 36px -30px hsl(var(--primary) / 0.12)',
height: `${height}px`,
};
});
const ambientStyle: CSSProperties = {
backgroundImage:
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.24) 0%, transparent 38%), linear-gradient(180deg, hsl(var(--nav-sheen) / 0.14), transparent 62%)',
};
</script>
<template>
<section
:style="style"
class="border-border bg-background flex w-full border-b transition-all"
class="relative flex w-full overflow-hidden transition-all"
>
<slot></slot>
<div :style="ambientStyle" class="pointer-events-none absolute inset-0"></div>
<div class="relative z-10 flex w-full">
<slot></slot>
</div>
</section>
</template>

View File

@@ -1,7 +1,16 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { ChevronsLeft, ChevronsRight } from '@easyflow-core/icons';
const collapsed = defineModel<boolean>('collapsed');
const buttonStyle: CSSProperties = {
backgroundColor: 'hsl(var(--nav-tool-bg) / 0.92)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-tool-bg) / 0.96), hsl(var(--glass-tint) / 0.82))',
border: '1px solid transparent',
boxShadow: '0 18px 36px -28px hsl(var(--primary) / 0.24)',
};
function handleCollapsed() {
collapsed.value = !collapsed.value;
@@ -9,11 +18,13 @@ function handleCollapsed() {
</script>
<template>
<div
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1"
<button
aria-label="切换侧边栏折叠状态"
:style="buttonStyle"
class="flex-center h-9 w-9 rounded-2xl text-[hsl(var(--nav-item-muted-foreground))] backdrop-blur-xl transition-[background-color,color,transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]"
@click.stop="handleCollapsed"
>
<ChevronsRight v-if="collapsed" class="size-4" />
<ChevronsLeft v-else class="size-4" />
</div>
</button>
</template>

View File

@@ -1,7 +1,16 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { Pin, PinOff } from '@easyflow-core/icons';
const expandOnHover = defineModel<boolean>('expandOnHover');
const buttonStyle: CSSProperties = {
backgroundColor: 'hsl(var(--nav-tool-bg) / 0.92)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-tool-bg) / 0.96), hsl(var(--glass-tint) / 0.82))',
border: '1px solid transparent',
boxShadow: '0 18px 36px -28px hsl(var(--primary) / 0.24)',
};
function toggleFixed() {
expandOnHover.value = !expandOnHover.value;
@@ -9,11 +18,13 @@ function toggleFixed() {
</script>
<template>
<div
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-[5px] transition-all duration-300"
<button
aria-label="切换侧边栏固定状态"
:style="buttonStyle"
class="flex-center h-9 w-9 rounded-2xl text-[hsl(var(--nav-item-muted-foreground))] backdrop-blur-xl transition-[background-color,color,transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]"
@click="toggleFixed"
>
<PinOff v-if="!expandOnHover" class="size-3.5" />
<Pin v-else class="size-3.5" />
</div>
</button>
</template>

View File

@@ -31,6 +31,24 @@ defineOptions({
name: 'EasyFlowLayout',
});
const headerToggleButtonClass =
'my-0 mr-1 flex h-9 w-9 items-center justify-center rounded-2xl text-[hsl(var(--nav-item-muted-foreground))] backdrop-blur-xl transition-[background-color,color,transform,box-shadow,border-color] hover:-translate-y-0.5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]';
const headerToggleButtonStyle: CSSProperties = {
backgroundColor: 'hsl(var(--nav-tool-bg) / 0.92)',
backgroundImage:
'linear-gradient(180deg, hsl(var(--nav-tool-bg) / 0.96), hsl(var(--glass-tint) / 0.8))',
border: '1px solid hsl(var(--glass-border) / 0.2)',
boxShadow: '0 18px 36px -28px hsl(var(--primary) / 0.24)',
};
const layoutAmbientLeftStyle: CSSProperties = {
backgroundImage:
'radial-gradient(circle at left top, hsl(var(--nav-ambient) / 0.34) 0%, hsl(var(--nav-ambient) / 0.14) 28%, transparent 72%)',
};
const layoutAmbientRightStyle: CSSProperties = {
backgroundImage:
'radial-gradient(circle at left top, hsl(var(--nav-ambient-secondary) / 0.14) 0%, transparent 72%)',
};
const props = withDefaults(defineProps<Props>(), {
contentCompact: 'wide',
contentCompactWidth: 1200,
@@ -482,6 +500,10 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
<template>
<div class="relative flex min-h-full w-full">
<div class="pointer-events-none absolute inset-0 overflow-hidden">
<div :style="layoutAmbientLeftStyle" class="absolute -left-16 top-0 h-[320px] w-[320px] rounded-full blur-3xl"></div>
<div :style="layoutAmbientRightStyle" class="absolute right-[-120px] top-[-40px] h-[260px] w-[320px] rounded-full blur-3xl"></div>
</div>
<LayoutSidebar
v-if="sidebarEnableState"
v-model:collapse="sidebarCollapse"
@@ -526,12 +548,12 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
<div
ref="contentRef"
class="flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
class="relative z-[1] flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in"
>
<div
:class="[
{
'shadow-[0_16px_24px_hsl(var(--background))]': scrollY > 20,
'shadow-[var(--shadow-toolbar)]': scrollY > 20,
},
SCROLL_FIXED_CLASS,
]"
@@ -556,7 +578,8 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
<template #toggle-button>
<EasyFlowIconButton
v-if="showHeaderToggleButton"
class="my-0 mr-1 rounded-md"
:class="headerToggleButtonClass"
:style="headerToggleButtonStyle"
@click="handleHeaderToggle"
>
<IconifyIcon v-if="showSidebar" icon="ep:fold" />