feat: 统一管理端弹窗与内容区交互样式
- 收敛管理端公共 Modal 链路,新增表单弹窗与普通内容弹窗包装\n- 迁移 Bot、知识库、插件、工作流、资源、MCP、数据中枢与系统管理页面级弹窗\n- 统一内容区工具栏、列表容器、导航与顶部按钮的视觉密度和交互节奏
This commit is contained in:
@@ -35,7 +35,7 @@ function handleClick(path?: string) {
|
||||
</script>
|
||||
<template>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbList class="easyflow-breadcrumb flex-nowrap">
|
||||
<TransitionGroup name="breadcrumb-transition">
|
||||
<template
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
@@ -44,10 +44,10 @@ function handleClick(path?: string) {
|
||||
<BreadcrumbItem>
|
||||
<div v-if="item.items?.length ?? 0 > 0">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="flex items-center gap-1">
|
||||
<EasyFlowIcon v-if="showIcon" :icon="item.icon" class="size-5" />
|
||||
{{ item.title }}
|
||||
<ChevronDown class="size-4" />
|
||||
<DropdownMenuTrigger class="easyflow-breadcrumb__link">
|
||||
<EasyFlowIcon v-if="showIcon" :icon="item.icon" class="size-4" />
|
||||
<span class="max-w-[180px] truncate">{{ item.title }}</span>
|
||||
<ChevronDown class="size-3.5" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<template
|
||||
@@ -63,32 +63,37 @@ function handleClick(path?: string) {
|
||||
</div>
|
||||
<BreadcrumbLink
|
||||
v-else-if="index !== breadcrumbs.length - 1"
|
||||
class="easyflow-breadcrumb__link"
|
||||
href="javascript:void 0"
|
||||
@click.stop="handleClick(item.path)"
|
||||
>
|
||||
<div class="flex-center">
|
||||
<div class="flex items-center">
|
||||
<EasyFlowIcon
|
||||
v-if="showIcon"
|
||||
:class="{ 'size-5': item.isHome }"
|
||||
:icon="item.icon"
|
||||
class="mr-1 size-4"
|
||||
/>
|
||||
{{ item.title }}
|
||||
<span class="max-w-[180px] truncate">{{ item.title }}</span>
|
||||
</div>
|
||||
</BreadcrumbLink>
|
||||
<BreadcrumbPage v-else>
|
||||
<div class="flex-center">
|
||||
<BreadcrumbPage
|
||||
v-else
|
||||
class="easyflow-breadcrumb__current"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<EasyFlowIcon
|
||||
v-if="showIcon"
|
||||
:class="{ 'size-5': item.isHome }"
|
||||
:icon="item.icon"
|
||||
class="mr-1 size-4"
|
||||
/>
|
||||
{{ item.title }}
|
||||
<span class="max-w-[220px] truncate">{{ item.title }}</span>
|
||||
</div>
|
||||
</BreadcrumbPage>
|
||||
<BreadcrumbSeparator
|
||||
v-if="index < breadcrumbs.length - 1 && !item.isHome"
|
||||
class="easyflow-breadcrumb__separator"
|
||||
/>
|
||||
</BreadcrumbItem>
|
||||
</template>
|
||||
@@ -96,3 +101,60 @@ function handleClick(path?: string) {
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.easyflow-breadcrumb {
|
||||
gap: 4px;
|
||||
color: hsl(var(--breadcrumb-muted));
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
color: hsl(var(--breadcrumb-muted));
|
||||
transition:
|
||||
color 0.16s ease,
|
||||
background-color 0.16s ease,
|
||||
transform 0.16s ease;
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__link:hover {
|
||||
background: hsl(var(--nav-item-hover) / 0.7);
|
||||
color: hsl(var(--nav-item-active-foreground));
|
||||
transform: translateY(-0.5px);
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__current {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
padding: 4px 10px;
|
||||
color: hsl(var(--breadcrumb-current));
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
hsl(var(--nav-item-active) / 0.88),
|
||||
hsl(var(--glass-tint) / 0.92)
|
||||
);
|
||||
border-radius: 999px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(var(--nav-sheen) / 0.42),
|
||||
0 10px 22px -18px hsl(var(--primary) / 0.22);
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__separator {
|
||||
color: hsl(var(--breadcrumb-muted) / 0.72);
|
||||
}
|
||||
|
||||
.easyflow-breadcrumb__separator :deep(svg) {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -72,12 +72,16 @@ defineExpose({
|
||||
</Transition>
|
||||
<AlertDialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position: 'fixed',
|
||||
backdropFilter: 'blur(var(--glass-blur)) saturate(170%)',
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
'z-popup border border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] p-6 shadow-[var(--shadow-float)] outline-none supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62] sm:rounded-[22px]',
|
||||
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
||||
|
||||
import type { CSSProperties } from 'vue';
|
||||
|
||||
import type { ClassType } from '@easyflow-core/typings';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -20,16 +22,19 @@ const props = withDefaults(
|
||||
class?: ClassType;
|
||||
closeClass?: ClassType;
|
||||
closeDisabled?: boolean;
|
||||
contentStyle?: CSSProperties;
|
||||
modal?: boolean;
|
||||
open?: boolean;
|
||||
overlayBlur?: number;
|
||||
overlayClass?: ClassType;
|
||||
overlayStyle?: CSSProperties;
|
||||
showClose?: boolean;
|
||||
zIndex?: number;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
appendTo: 'body',
|
||||
animationType: 'slide',
|
||||
animationType: 'scale',
|
||||
closeDisabled: false,
|
||||
showClose: true,
|
||||
},
|
||||
@@ -41,8 +46,11 @@ const emits = defineEmits<
|
||||
const delegatedProps = computed(() => {
|
||||
const {
|
||||
class: _,
|
||||
contentStyle: _contentStyle,
|
||||
modal: _modal,
|
||||
open: _open,
|
||||
overlayClass: _overlayClass,
|
||||
overlayStyle: _overlayStyle,
|
||||
showClose: __,
|
||||
animationType: ___,
|
||||
...delegated
|
||||
@@ -86,9 +94,11 @@ defineExpose({
|
||||
<Transition name="fade">
|
||||
<DialogOverlay
|
||||
v-if="open && modal"
|
||||
:class="props.overlayClass"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
...props.overlayStyle,
|
||||
backdropFilter:
|
||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||
}"
|
||||
@@ -97,14 +107,18 @@ defineExpose({
|
||||
</Transition>
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
:style="{ ...(zIndex ? { zIndex } : {}), position }"
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
...props.contentStyle,
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
'z-popup data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-220 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full border border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] p-6 shadow-[var(--shadow-float)] outline-none data-[state=closed]:duration-150 data-[state=closed]:ease-in data-[state=open]:ease-out supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62] sm:rounded-[22px]',
|
||||
{
|
||||
'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':
|
||||
'data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-4':
|
||||
animationType === 'slide',
|
||||
},
|
||||
props.class,
|
||||
@@ -118,7 +132,7 @@ defineExpose({
|
||||
:disabled="closeDisabled"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-3 top-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none',
|
||||
'data-[state=open]:text-muted-foreground hover:text-accent-foreground text-foreground/80 flex-center opacity-78 absolute right-3 top-3 h-7 w-7 rounded-full px-1 text-lg shadow-[0_10px_24px_-24px_hsl(var(--foreground)/0.34)] transition-opacity hover:bg-[hsl(var(--surface-contrast-soft))/0.98] hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-[hsl(var(--surface-contrast-soft))/0.92]',
|
||||
props.closeClass,
|
||||
)
|
||||
"
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from 'vue';
|
||||
import { computed, inject, useAttrs } from 'vue';
|
||||
|
||||
import { useScrollLock } from '@easyflow-core/composables';
|
||||
import { cn } from '@easyflow-core/shared/utils';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
useScrollLock();
|
||||
|
||||
const attrs = useAttrs();
|
||||
const id = inject('DISMISSABLE_MODAL_ID');
|
||||
const overlayClass = computed(() => {
|
||||
const customClass = attrs.class as string | undefined;
|
||||
return cn(
|
||||
customClass ? 'z-popup inset-0' : 'bg-overlay z-popup inset-0',
|
||||
customClass,
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :data-dismissable-modal="id" class="bg-overlay z-popup inset-0"></div>
|
||||
<div
|
||||
:data-dismissable-modal="id"
|
||||
:class="overlayClass"
|
||||
:style="$attrs.style"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
@@ -91,6 +91,7 @@ function onAnimationEnd(event: AnimationEvent) {
|
||||
:style="{
|
||||
...(zIndex ? { zIndex } : {}),
|
||||
position,
|
||||
backdropFilter: 'blur(var(--glass-blur)) saturate(170%)',
|
||||
}"
|
||||
@animationend="onAnimationEnd"
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export const sheetVariants = cva(
|
||||
'bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',
|
||||
'border-[hsl(var(--glass-border))/0.18] bg-[hsl(var(--glass-tint))/0.84] shadow-[var(--shadow-float)] transition ease-in-out supports-[backdrop-filter]:bg-[hsl(var(--glass-tint))/0.62] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||
{
|
||||
defaultVariants: {
|
||||
side: 'right',
|
||||
@@ -11,11 +11,11 @@ export const sheetVariants = cva(
|
||||
variants: {
|
||||
side: {
|
||||
bottom:
|
||||
'inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
||||
'inset-x-0 bottom-0 border-t border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
||||
right:
|
||||
'inset-y-0 right-0 w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
'inset-y-0 right-0 w-3/4 border-l border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||
top: 'inset-x-0 top-0 border-b border-[hsl(var(--divider-faint))/0.3] data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user