This commit is contained in:
Json_Lee
2025-06-02 11:43:19 +08:00
14 changed files with 689 additions and 570 deletions
+150
View File
@@ -0,0 +1,150 @@
<!-- 文件上传 -->
<script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import { useFileDialog } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { useFilesStore } from '@/stores/modules/files';
type FilesList = FilesCardProps & {
file: File;
};
const filesStore = useFilesStore();
/* 弹出面板 开始 */
const popoverStyle = ref({
padding: '4px',
height: 'fit-content',
background: 'var(--el-bg-color, #fff)',
border: '1px solid var(--el-border-color-light)',
borderRadius: '8px',
boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
});
const popoverRef = ref();
/* 弹出面板 结束 */
const { reset, open, onChange } = useFileDialog({
// 允许所有图片文件,文档文件,音视频文件
accept: 'image/*,video/*,audio/*,application/*',
directory: false, // 是否允许选择文件夹
multiple: true, // 是否允许多选
});
onChange((files) => {
if (!files)
return;
console.log('files', files);
const arr = [] as FilesList[];
for (let i = 0; i < files!.length; i++) {
const file = files![i];
arr.push({
uid: crypto.randomUUID(), // 不写 uid,文件列表展示不出来,elx 1.2.0 bug 待修复
name: file.name,
fileSize: file.size,
file,
maxWidth: '200px',
showDelIcon: true, // 显示删除图标
imgPreview: true, // 显示图片预览
imgVariant: 'square', // 图片预览的形状
url: URL.createObjectURL(file), // 图片预览地址
});
}
filesStore.setFilesList([...filesStore.filesList, ...arr]);
// 重置文件选择器
nextTick(() => reset());
});
function handleUploadFiles() {
open();
popoverRef.value.hide();
}
</script>
<template>
<div class="files-select">
<Popover
ref="popoverRef"
placement="top-start"
:offset="[4, 0]"
popover-class="popover-content"
:popover-style="popoverStyle"
trigger="clickTarget"
>
<template #trigger>
<div
class="flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px border-1px border-[rgba(0,0,0,0.08)] border-solid hover:bg-[rgba(0,0,0,.04)]"
>
<el-icon>
<Paperclip />
</el-icon>
</div>
</template>
<div class="popover-content-box">
<div
class="popover-content-item flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px hover:bg-[rgba(0,0,0,.04)]"
@click="handleUploadFiles"
>
<el-icon>
<Upload />
</el-icon>
<div class="font-size-14px">
上传文件或图片
</div>
</div>
<Popover
placement="right-end"
:offset="[8, 4]"
popover-class="popover-content"
:popover-style="popoverStyle"
trigger="hover"
:hover-delay="100"
>
<template #trigger>
<div
class="popover-content-item flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px hover:bg-[rgba(0,0,0,.04)]"
>
<SvgIcon name="code" size="16" />
<div class="font-size-14px">
上传代码
</div>
<el-icon class="ml-auto">
<ArrowRight />
</el-icon>
</div>
</template>
<div class="popover-content-box">
<div
class="popover-content-item flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px hover:bg-[rgba(0,0,0,.04)]"
@click="
() => {
ElMessage.warning('暂未开放');
}
"
>
代码文件
</div>
<div
class="popover-content-item flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px hover:bg-[rgba(0,0,0,.04)]"
@click="
() => {
ElMessage.warning('暂未开放');
}
"
>
代码文件夹
</div>
</div>
</Popover>
</div>
</Popover>
</div>
</template>
<style scoped lang="scss"></style>
+43 -24
View File
@@ -28,6 +28,10 @@ const popoverStyle = ref({
width: '200px',
padding: '4px',
height: 'fit-content',
background: 'var(--el-bg-color, #fff)',
border: '1px solid var(--el-border-color-light)',
borderRadius: '8px',
boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
});
const popoverRef = ref();
@@ -45,25 +49,25 @@ function handleClick(item: GetSessionListVO) {
</script>
<template>
<div class="model-switch">
<div class="model-select">
<Popover
ref="popoverRef"
position="top-start"
:offset="[-14, 10]"
:trigger-style="{ cursor: 'pointer' }"
placement="top-start"
:offset="[4, 0]"
popover-class="popover-content"
:popover-style="popoverStyle"
trigger="clickTarget"
@show="showPopover"
>
<!-- 触发元素插槽 -->
<template #trigger>
<div
class="model-switch-box select-none flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-12px border-[rgba()]"
class="model-select-box select-none flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-12px border-[rgba()]"
>
<div class="model-switch-box-icon">
<div class="model-select-box-icon">
<SvgIcon name="models" size="12" />
</div>
<div class="model-switch-box-text font-size-12px">
<div class="model-select-box-text font-size-12px">
{{ currentModelName }}
</div>
</div>
@@ -73,30 +77,30 @@ function handleClick(item: GetSessionListVO) {
<div
v-for="item in popoverList"
:key="item.id"
class="popover-content-box-items flex rounded-8px select-none transition-all transition-duration-300 flex items-center gap-8px p-4px hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]"
class="popover-content-box-items w-full rounded-8px select-none transition-all transition-duration-300 flex items-center hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]"
>
<el-tooltip
popper-class="rounded-tooltip"
effect="dark"
<Popover
trigger-class="popover-trigger-item-text"
popover-class="rounded-tooltip"
placement="right"
trigger="hover"
:offset="10"
:show-arrow="false"
transition="zoom-fade"
:offset="[12, 0]"
>
<template #content>
<div class="popover-content-box-item-text text-wrap max-w-200px rounded-lg">
{{ item.remark }}
<template #trigger>
<div
class="popover-content-box-item p-4px font-size-12px text-overflow line-height-16px"
:class="{ 'bg-[rgba(0,0,0,.04)] is-select': item.modelName === currentModelName }"
@click="handleClick(item)"
>
{{ item.modelName }}
</div>
</template>
<div
class="popover-content-box-item font-size-12px text-overflow w-full line-height-16px"
:class="{ 'bg-[rgba(0,0,0,.04)] is-select': item.modelName === currentModelName }"
@click="handleClick(item)"
class="popover-content-box-item-text text-wrap max-w-200px rounded-lg p-8px font-size-12px line-height-tight"
>
{{ item.modelName }}
{{ item.remark }}
</div>
</el-tooltip>
</Popover>
</div>
</div>
</Popover>
@@ -104,7 +108,7 @@ function handleClick(item: GetSessionListVO) {
</template>
<style scoped lang="scss">
.model-switch-box {
.model-select-box {
color: var(--el-color-primary, #409eff);
border: 1px solid var(--el-color-primary, #409eff);
border-radius: 10px;
@@ -116,20 +120,35 @@ function handleClick(item: GetSessionListVO) {
}
.popover-content-box {
// background-color: red;
height: 200px;
gap: 4px;
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
.popover-content-box-items {
:deep() {
.popover-trigger-item-text {
width: 100%;
}
}
}
.popover-content-box-item-text {
background-color: black;
color: white;
}
// 滚动条样式
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: #f5f5f5;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 4px;
-431
View File
@@ -1,431 +0,0 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { onClickOutside, useEventListener } from '@vueuse/core';
type PopoverPosition =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end';
type Offset = [number, number];
const props = withDefaults(defineProps<PopoverProps>(), {
position: 'bottom',
offset: () => [8, 8],
boundary: 'viewport',
closeOnContentClick: false,
closeOnTriggerClick: false,
triggerStyle: () => ({}),
popoverStyle: () => ({}),
popoverClass: '',
});
const emits = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
(e: 'positionChange', pos: PopoverPosition): void;
}>();
const VIEWPORT_PADDING = 16;
interface PopoverProps {
position?: PopoverPosition;
offset?: Offset;
triggerStyle?: CSSProperties;
popoverStyle?: CSSProperties;
popoverClass?: string;
boundary?: 'viewport' | HTMLElement;
closeOnContentClick?: boolean;
closeOnTriggerClick?: boolean;
}
const triggerRef = ref<HTMLElement | null>(null);
const popoverRef = ref<HTMLElement | null>(null);
const showPoperContent = ref(false);
const currentPosition = ref<PopoverPosition>(props.position);
let resizeObserver: ResizeObserver | null = null;
let mutationObserver: MutationObserver | null = null;
let updatePositionTimeout: number | null = null;
// 初始化尺寸与内容变化监听
function initObservers() {
if (!popoverRef.value)
return;
// 监听弹出框尺寸变化
resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(updatePosition);
});
resizeObserver.observe(popoverRef.value);
// 监听弹出框内容变化(子节点/样式变更)
mutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
updatePosition();
}
}
requestAnimationFrame(updatePosition);
});
mutationObserver.observe(popoverRef.value, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class'],
});
}
// 销毁监听
function destroyObservers() {
resizeObserver?.disconnect();
mutationObserver?.disconnect();
resizeObserver = null;
mutationObserver = null;
}
// 强制更新定位(处理DOM缓存)
function updatePosition() {
if (!triggerRef.value || !popoverRef.value)
return;
// 强制触发回流获取最新尺寸
triggerRef.value.getBoundingClientRect();
popoverRef.value.getBoundingClientRect();
const triggerRect = triggerRef.value.getBoundingClientRect();
const popoverRect = popoverRef.value.getBoundingClientRect();
const boundaryRect = getBoundaryRect();
const adjustedPos = adjustPosition(triggerRect, popoverRect, boundaryRect);
currentPosition.value = adjustedPos;
emits('positionChange', adjustedPos);
const { top, left, origin } = calculatePosition(triggerRect, popoverRect, adjustedPos);
popoverRef.value.style.top = `${top}px`;
popoverRef.value.style.left = `${left}px`;
popoverRef.value.style.transformOrigin = origin;
}
// 窗口尺寸变化防抖处理
useEventListener('resize', () => {
if (updatePositionTimeout)
clearTimeout(updatePositionTimeout);
updatePositionTimeout = setTimeout(updatePosition, 50);
});
// 显示/隐藏状态监听
watch(
showPoperContent,
(newVal) => {
if (newVal) {
nextTick(() => {
initObservers();
updatePosition();
});
}
else {
destroyObservers();
}
},
{ immediate: true },
);
// 边界计算(保持不变)
function getBoundaryRect(): DOMRect {
if (props.boundary === 'viewport') {
return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
}
return (props.boundary as HTMLElement).getBoundingClientRect();
}
// 定位计算(保持不变)
function calculatePosition(triggerRect: DOMRect, popoverRect: DOMRect, position: PopoverPosition) {
const [offsetX, offsetY] = props.offset!;
const { width: tW, height: tH } = triggerRect;
const { width: pW, height: pH } = popoverRect;
const positionMap: Record<PopoverPosition, { top: number; left: number; origin: string }> = {
'top': {
top: triggerRect.top - pH - offsetY,
left: triggerRect.left + tW / 2 - pW / 2 + offsetX,
origin: 'bottom center',
},
'top-start': {
top: triggerRect.top - pH - offsetY,
left: triggerRect.left + offsetX,
origin: 'bottom left',
},
'top-end': {
top: triggerRect.top - pH - offsetY,
left: triggerRect.left + tW - pW + offsetX,
origin: 'bottom right',
},
'bottom': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + tW / 2 - pW / 2 + offsetX,
origin: 'top center',
},
'bottom-start': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + offsetX,
origin: 'top left',
},
'bottom-end': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + tW - pW + offsetX,
origin: 'top right',
},
'left': {
top: triggerRect.top + tH / 2 - pH / 2 + offsetY,
left: triggerRect.left - pW - offsetX,
origin: 'right center',
},
'left-start': {
top: triggerRect.top + offsetY,
left: triggerRect.left - pW - offsetX,
origin: 'right top',
},
'left-end': {
top: triggerRect.top + tH - pH + offsetY,
left: triggerRect.left - pW - offsetX,
origin: 'right bottom',
},
'right': {
top: triggerRect.top + tH / 2 - pH / 2 + offsetY,
left: triggerRect.right + offsetX,
origin: 'left center',
},
'right-start': {
top: triggerRect.top + offsetY,
left: triggerRect.right + offsetX,
origin: 'left top',
},
'right-end': {
top: triggerRect.top + tH - pH + offsetY,
left: triggerRect.right + offsetX,
origin: 'left bottom',
},
};
return positionMap[position];
}
// 边界调整(保持不变)
function adjustPosition(
triggerRect: DOMRect,
popoverRect: DOMRect,
boundaryRect: DOMRect,
): PopoverPosition {
const allPositions: PopoverPosition[] = [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
];
const candidatePositions = [props.position, ...allPositions.filter(p => p !== props.position)];
for (const pos of candidatePositions) {
const { top, left } = calculatePosition(triggerRect, popoverRect, pos);
if (
top >= boundaryRect.top + VIEWPORT_PADDING
&& left >= boundaryRect.left + VIEWPORT_PADDING
&& top + popoverRect.height <= boundaryRect.bottom - VIEWPORT_PADDING
&& left + popoverRect.width <= boundaryRect.right - VIEWPORT_PADDING
) {
return pos;
}
}
return props.position;
}
// 交互逻辑(保持不变)
function handleTriggerClick() {
if (showPoperContent.value) {
props.closeOnTriggerClick && hidePopover();
}
else {
showPoperContent.value = true;
nextTick(() => emits('show'));
}
}
function handleContentClick(e: MouseEvent) {
props.closeOnContentClick && hidePopover();
e.stopPropagation();
}
function hidePopover() {
showPoperContent.value = false;
emits('hide');
}
onClickOutside(popoverRef, () => !props.closeOnTriggerClick && hidePopover(), {
ignore: [triggerRef],
});
onUnmounted(() => {
destroyObservers();
updatePositionTimeout && clearTimeout(updatePositionTimeout);
});
defineExpose({ show: () => (showPoperContent.value = true), hide: hidePopover });
</script>
<template>
<div
ref="triggerRef"
:style="props.triggerStyle"
role="button"
aria-haspopup="true"
:aria-expanded="showPoperContent"
@click.stop="handleTriggerClick"
>
<slot name="trigger" />
</div>
<Teleport to="body">
<Transition name="popover-fade" @before-enter="updatePosition">
<div
v-if="showPoperContent"
ref="popoverRef"
class="popover-content"
:style="props.popoverStyle"
:class="[props.popoverClass]"
role="dialog"
aria-modal="false"
:data-popper-placement="currentPosition"
@click="handleContentClick"
>
<slot name="header" />
<slot />
<slot name="footer" />
</div>
</Transition>
</Teleport>
</template>
<style lang="scss">
.popover-fade-enter-active,
.popover-fade-leave-active {
transition:
opacity 0.2s ease,
transform 0.2s ease;
will-change: transform, opacity;
}
.popover-fade-enter-from,
.popover-fade-leave-to {
opacity: 0;
transform: scale(0.95);
}
.popover-fade-enter-to,
.popover-fade-leave-from {
opacity: 1;
transform: scale(1);
}
.popover-content {
position: fixed;
min-width: 120px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 12px 16px;
z-index: 1000;
&::before {
content: "";
position: absolute;
width: 0;
height: 0;
border: 6px solid transparent;
}
[data-popper-placement^="top"]::before {
top: 100%;
border-top-color: #e5e7eb;
}
[data-popper-placement="top"]::before {
left: 50%;
transform: translateX(-50%);
}
[data-popper-placement="top-start"]::before {
left: 16px;
}
[data-popper-placement="top-end"]::before {
right: 16px;
}
[data-popper-placement^="bottom"]::before {
bottom: 100%;
border-bottom-color: #e5e7eb;
}
[data-popper-placement="bottom"]::before {
left: 50%;
transform: translateX(-50%);
}
[data-popper-placement="bottom-start"]::before {
left: 16px;
}
[data-popper-placement="bottom-end"]::before {
right: 16px;
}
[data-popper-placement^="left"]::before {
left: 100%;
border-left-color: #e5e7eb;
}
[data-popper-placement="left"]::before {
top: 50%;
transform: translateY(-50%);
}
[data-popper-placement="left-start"]::before {
top: 16px;
}
[data-popper-placement="left-end"]::before {
bottom: 16px;
}
[data-popper-placement^="right"]::before {
right: 100%;
border-right-color: #e5e7eb;
}
[data-popper-placement="right"]::before {
top: 50%;
transform: translateY(-50%);
}
[data-popper-placement="right-start"]::before {
top: 16px;
}
[data-popper-placement="right-end"]::before {
bottom: 16px;
}
}
</style>
+165 -52
View File
@@ -1,31 +1,9 @@
<!-- Popover 弹框 -->
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { flip, offset, shift, useFloating } from '@floating-ui/vue';
import { arrow, autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue';
import { onClickOutside } from '@vueuse/core';
const props = withDefaults(defineProps<PopoverProps>(), {
position: 'bottom',
offset: () => [8, 8],
boundary: 'viewport',
closeOnContentClick: false,
closeOnTriggerClick: false,
triggerStyle: () => ({}),
popoverStyle: () => ({}),
popoverClass: '',
});
const emits = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
(e: 'positionChange', pos: PopoverPosition): void;
}>();
const reference = ref();
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
placement: props.position,
middleware: [offset(props.offset[0]), flip(), shift()],
});
type PopoverPosition =
export type PopoverPlacement =
| 'top'
| 'top-start'
| 'top-end'
@@ -39,54 +17,189 @@ type PopoverPosition =
| 'right-start'
| 'right-end';
type Offset = [number, number];
interface PopoverProps {
position?: PopoverPosition;
export type Offset = [number, number];
export interface PopoverProps {
placement?: PopoverPlacement;
offset?: Offset;
triggerStyle?: CSSProperties;
popoverStyle?: CSSProperties;
popoverClass?: string;
boundary?: 'viewport' | HTMLElement;
closeOnContentClick?: boolean;
closeOnTriggerClick?: boolean;
trigger?: 'hover' | 'click' | 'clickTarget';
triggerStyle?: CSSProperties;
triggerClass?: string;
hoverDelay?: number; // 悬停延迟关闭时间(ms)
}
const showPoperContent = ref(false);
const props = withDefaults(defineProps<PopoverProps>(), {
placement: 'bottom',
offset: () => [0, 0],
trigger: 'hover',
hoverDelay: 0, // 默认300ms延迟关闭
});
function handleTriggerClick() {
showPoperContent.value = !showPoperContent.value;
if (showPoperContent.value) {
const emits = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
}>();
const triggerRef = ref<HTMLElement | null>(null);
const popoverRef = ref<HTMLElement | null>(null);
const floatingArrow = ref<HTMLElement | null>(null);
const showPopover = ref(false);
let hideTimeout: ReturnType<typeof setTimeout> | null = null;
// 新增:记录鼠标是否在触发元素或内容区域内
const isHovering = ref(false);
const { floatingStyles } = useFloating(triggerRef, popoverRef, {
placement: props.placement,
transform: false,
whileElementsMounted: autoUpdate,
middleware: [
shift(),
flip(),
arrow({ element: floatingArrow }),
offset({
mainAxis: props.offset[0],
crossAxis: props.offset[1],
}),
],
});
function show() {
if (!showPopover.value) {
showPopover.value = true;
emits('show');
}
else {
// 显示时强制清除定时器(无论是否在悬停)
if (hideTimeout)
clearTimeout(hideTimeout);
hideTimeout = null;
}
function hide() {
if (showPopover.value) {
showPopover.value = false;
emits('hide');
}
hideTimeout = null;
}
defineExpose({ show, hide });
watch(showPopover, (newValue) => {
if (newValue && props.trigger !== 'hover') {
onClickOutside(popoverRef, () => hide(), {
ignore: [triggerRef] as any[],
});
}
});
// 触发元素鼠标事件调整(同步isHovering状态)
function handleTriggerMouseEnter() {
if (props.trigger === 'hover') {
isHovering.value = true; // 进入触发元素
show();
}
}
function handleTriggerMouseLeave() {
if (props.trigger === 'hover') {
isHovering.value = false; // 离开触发元素
// 仅当鼠标不在内容区域时,才设置延迟关闭
scheduleHideIfNeeded();
}
}
// 内容区域鼠标事件调整(同步isHovering状态)
function handlePopoverMouseEnter() {
if (props.trigger === 'hover') {
isHovering.value = true; // 进入内容区域
if (hideTimeout)
clearTimeout(hideTimeout); // 取消关闭
}
}
function handlePopoverMouseLeave() {
if (props.trigger === 'hover') {
isHovering.value = false; // 离开内容区域
// 仅当鼠标不在触发元素时,才设置延迟关闭
scheduleHideIfNeeded();
}
}
// 新增:统一延迟关闭逻辑(仅当完全离开两个区域时触发)
function scheduleHideIfNeeded() {
// 如果鼠标仍在任一区域(isHovering为true),不关闭
if (isHovering.value)
return;
// 否则设置延迟关闭
hideTimeout = setTimeout(() => {
if (!isHovering.value) {
// 再次确认是否仍离开
hide();
}
}, props.hoverDelay);
}
function handleClick() {
if (props.trigger === 'click') {
showPopover.value ? hide() : show();
}
else if (props.trigger === 'clickTarget' && !showPopover.value) {
show();
}
}
</script>
<template>
<div>
<div ref="reference" :style="props.triggerStyle" @click.stop="handleTriggerClick">
<slot name="trigger" />
</div>
<div
ref="triggerRef"
class="popover-trigger"
:class="[props.triggerClass]"
:style="[props.triggerStyle]"
@mouseenter="handleTriggerMouseEnter"
@mouseleave="handleTriggerMouseLeave"
@click="handleClick"
>
<slot name="trigger" />
</div>
<Teleport to="body">
<Transition name="popover-fade">
<div
v-if="showPoperContent"
ref="floating"
class="popover-content"
:style="{
...floatingStyles,
...props.popoverStyle,
}"
v-if="showPopover"
ref="popoverRef"
:style="[floatingStyles, props.popoverStyle]"
class="popover-content-box"
:class="[props.popoverClass]"
@mouseenter="handlePopoverMouseEnter"
@mouseleave="handlePopoverMouseLeave"
>
<slot name="header" />
<slot />
<slot name="footer" />
</div>
</Transition>
</div>
</Teleport>
</template>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.popover-fade-enter-active,
.popover-fade-leave-active {
transition:
opacity 0.2s ease,
transform 0.2s ease;
will-change: transform, opacity;
}
.popover-fade-enter-from,
.popover-fade-leave-to {
opacity: 0;
transform: scale(0.95);
}
.popover-fade-enter-to,
.popover-fade-leave-from {
opacity: 1;
transform: scale(1);
}
</style>