feat: 统一管理端弹窗与内容区交互样式
- 收敛管理端公共 Modal 链路,新增表单弹窗与普通内容弹窗包装\n- 迁移 Bot、知识库、插件、工作流、资源、MCP、数据中枢与系统管理页面级弹窗\n- 统一内容区工具栏、列表容器、导航与顶部按钮的视觉密度和交互节奏
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user