Layer
组件介绍
弹出后可调整各个方向大小以及位置
使用示例
基本使用
点我查看代码
vue
<script lang="ts" setup>
import type { Rect } from '@summeruse/ui'
import { NButton, NCard } from 'naive-ui'
import { ref } from 'vue'
import Layer from './index.vue'
const props = defineProps<{
initRect?: Rect
}>()
const show = ref(true)
const initRect = ref(props.initRect ?? {
x: 500,
y: 500,
width: 200,
height: 200,
})
setTimeout(() => {
initRect.value = {
x: 300,
y: 500,
width: 300,
height: 300,
}
}, 3000)
const teleport = ref(true)
</script>
<template>
<NButton @click="show = !show">
{{ show ? '关闭' : "打开" }}
</NButton>
<NButton @click="teleport = !teleport">
{{ teleport ? '返回' : "弹出" }}
</NButton>
<Layer
v-model:show="show" v-model:init-rect="initRect" :teleport="teleport" on-top
>
<template #default="{ close }">
<slot :close :show>
<NCard header-class="layer-header" title="标题" class="w-100% h-100%">
<template #header-extra>
<div class="h-20px flex cursor-pointer hover:bg-#abf5 w-20px items-center justify-center" @click="close">
<div>x</div>
</div>
</template>
</NCard>
</slot>
</template>
</Layer>
</template>
组件代码
点我查看代码
vue
<script lang="ts" setup>
import type { StyleValue } from 'vue'
import type { LayerProps } from './props'
import { useResizeObserver } from '@vueuse/core'
import { computed, ref, toRefs, useTemplateRef, watch } from 'vue'
import { useLayerIndexManager } from './layer-provider'
import { useLayer } from './useLayer/index'
const props = withDefaults(defineProps<LayerProps>(), {
to: 'body',
destroyOnClose: true,
})
const propsRef = toRefs(props)
const show = defineModel<boolean>('show', {
required: true,
})
const layerRef = useTemplateRef('layer')
const contentRef = useTemplateRef('content')
const rectModel = defineModel<{
x?: number
y?: number
width?: number
height?: number
}>('initRect', {
required: true,
})
const initRect = ref({
x: 0,
y: 0,
width: 0,
height: 0,
...rectModel.value,
})
const layerIndexManager = useLayerIndexManager()
const zIndex = ref(propsRef.onTop.value ? layerIndexManager.nextZIndex() : layerIndexManager.defaultZIndex)
const hidden = computed(() => {
if (!propsRef.teleport.value) {
return false
}
return !show.value
})
const destroyed = computed(() => {
if (!propsRef.teleport.value) {
return false
}
return propsRef.destroyOnClose.value && !show.value
})
const disabledDrag = computed(() => {
if (!propsRef.teleport.value) {
return true
}
return propsRef.disabledDrag.value
})
const disabledResize = computed(() => {
if (!propsRef.teleport.value) {
return true
}
if (!rectModel.value.width || !rectModel.value.height) {
return true
}
return propsRef.disabledResize.value
})
const { rect, check } = useLayer(layerRef, {
...propsRef,
disabledDrag,
disabledResize,
initRect,
})
useResizeObserver(contentRef, (entries) => {
const entry = entries[0]
if (disabledResize.value && entry) {
const { width, height } = entry.target.getBoundingClientRect()
rect.value.width = width
rect.value.height = height
check()
}
})
watch(propsRef.teleport, (teleport) => {
if (teleport && show.value) {
check()
}
})
watch(() => rectModel.value.height, (value) => {
if (value) {
rect.value.height = value
check()
}
})
watch(() => rectModel.value.width, (value) => {
if (value) {
rect.value.width = value
check()
}
})
watch(() => rectModel.value.x, (value) => {
if (value) {
rect.value.x = value
check()
}
})
watch(() => rectModel.value.y, (value) => {
if (value) {
rect.value.y = value
check()
}
})
const style = computed<StyleValue>(() => {
const style: StyleValue = !hidden.value
? [{
display: 'block',
}]
: [{
display: 'none',
}]
if (propsRef.teleport.value) {
style.push({
position: 'fixed',
zIndex: zIndex.value,
left: `${rect.value.x}px`,
top: `${rect.value.y}px`,
})
if (!propsRef.disabledResize.value) {
style.push({
width: `${rect.value.width}px`,
height: `${rect.value.height}px`,
})
}
}
return style
})
function close() {
show.value = false
}
function handleTop() {
if (!propsRef.teleport.value) {
return
}
if (!propsRef.onTop.value) {
return
}
zIndex.value = layerIndexManager.nextZIndex()
}
</script>
<template>
<Teleport :to="to" :disabled="!teleport">
<div v-if="!destroyed" v-bind="$attrs" ref="layer" :style @mousedown="handleTop">
<div ref="content" style="width: 100%; height: 100%;">
<slot :close />
</div>
</div>
</Teleport>
</template>
Props
点我查看代码
ts
import type { RendererElement, VNode } from 'vue'
import type { Directions, Rect } from './useLayer/index'
export interface LayerProps {
/** 初始位置 */
initRect?: {
x?: number
y?: number
width?: number
height?: number
}
/** teleport 目标容器 */
to?: string | RendererElement | null | undefined
/** 是否弹出到Teleport */
teleport?: boolean
/** 方向 */
directions?: Directions
/** 拖动元素 */
dragElement?: HTMLElement
/** 禁止拉伸 */
disabledResize?: boolean
/** 禁止拖动 */
disabledDrag?: boolean
/** 最小宽度 */
minWidth?: number
/** 最小高度 */
minHeight?: number
/** 最大宽度 */
maxWidth?: number
/** 最大高度 */
maxHeight?: number
/** 宽高比 */
ratio?: number
/** 限位元素 */
parent?: HTMLElement
/** 允许拉伸到父元素外面 */
allowOverParent?: boolean
/** 保持置顶 */
onTop?: boolean
/** 关闭时销毁 */
destroyOnClose?: boolean
}
export type UseLayerOptions = LayerProps & {
initRect: Rect
content?: ((close: () => void) => VNode) | string
}
ts
import type { MaybeRefOrGetter } from 'vue'
export type ResizeDirection = 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
export interface Directions {
'left'?: boolean
'right'?: boolean
'top'?: boolean
'bottom'?: boolean
'top-left'?: boolean
'top-right'?: boolean
'bottom-left'?: boolean
'bottom-right'?: boolean
}
export interface Rect {
height: number
width: number
x: number
y: number
}
export interface LayerOptions {
// 方向
directions?: MaybeRefOrGetter<Directions | undefined>
// 初始位置
initRect?: MaybeRefOrGetter<Rect>
// 拖动元素
dragElement?: MaybeRefOrGetter<HTMLElement | undefined>
// 禁止拉伸
disabledResize?: MaybeRefOrGetter<boolean>
// 禁止拖动
disabledDrag?: MaybeRefOrGetter<boolean>
// 最小宽度
minWidth?: MaybeRefOrGetter<number | undefined>
// 最小高度
minHeight?: MaybeRefOrGetter<number | undefined>
// 最大宽度
maxWidth?: MaybeRefOrGetter<number | undefined>
// 最大高度
maxHeight?: MaybeRefOrGetter<number | undefined>
// 宽高比
ratio?: MaybeRefOrGetter<number | undefined>
// 限位元素
parent?: MaybeRefOrGetter<HTMLElement | undefined>
// 允许拉伸到父元素外面
allowOverParent?: MaybeRefOrGetter<boolean>
}