258 lines
6.2 KiB
Vue
258 lines
6.2 KiB
Vue
<script setup lang="ts">
|
||
// Ni组件消息基础弹窗容器
|
||
const props = defineProps({
|
||
status: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
maskClosable: {
|
||
// 点击遮罩层是否能够关闭,默认可以关闭
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
escKeyClosable: {
|
||
// 按下Esc层是否能够关闭,默认可以关闭
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
width:{
|
||
// 宽度
|
||
type: String,
|
||
default: '600'
|
||
}
|
||
})
|
||
// 弹窗本身
|
||
const NiPopup = ref(null)
|
||
const NiPopupContainer = ref(null)
|
||
// 开/关状态
|
||
const visible = ref(props.status)
|
||
// 主体宽度
|
||
const width = ref(props.width)
|
||
// 监听弹窗状态值
|
||
watch(() => props.status, (val) => {
|
||
visible.value = val;
|
||
})
|
||
// 监听弹窗主体宽度
|
||
watch(() => props.width, (val) => {
|
||
width.value = val;
|
||
setPopupWitdh(val);
|
||
})
|
||
// 定义触发父组件事件 其中update需要 v-module:status="popupStatus" || v-module="status" || :status + @update=status
|
||
const emit = defineEmits(['update:status', 'handleClose'])
|
||
// 聚焦指令
|
||
const vFocus = {
|
||
// 单独设置没用,需要在标签中设置 tabindex="0"
|
||
mounted: (el) => {
|
||
el.focus()
|
||
}
|
||
}
|
||
function setPopupWitdh(val: string) {
|
||
if(parseInt(val)){
|
||
nextTick(() => {
|
||
NiPopupContainer.value?.style.setProperty('--width', `${val}px`)
|
||
})
|
||
}else{
|
||
throw new Error('弹窗宽度需要正确的数字!')
|
||
}
|
||
}
|
||
// 关闭弹窗
|
||
const handleClose = () => {
|
||
emit('update:status', false)
|
||
emit('handleClose')
|
||
}
|
||
// Esc关闭
|
||
const handleEscClose = () => {
|
||
if(props.escKeyClosable){
|
||
handleClose()
|
||
}
|
||
}
|
||
// 遮罩层关闭
|
||
const handleMaskClickClose = () => {
|
||
if(props.maskClosable){
|
||
handleClose()
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
setPopupWitdh(width.value)
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<teleport to="#teleports">
|
||
<Transition name="NiPopup" data-prefix="Ni">
|
||
<div
|
||
v-if="visible"
|
||
ref="NiPopup"
|
||
v-focus
|
||
tabindex="0"
|
||
class="NiPopup allCenter"
|
||
@keydown.esc="handleEscClose"
|
||
@click.self="handleMaskClickClose"
|
||
>
|
||
<div ref="NiPopupContainer" :style="{ '--width': width + 'px' }" class="NiPopupContainer">
|
||
<header>
|
||
<div class="logo"><NiLogo/></div>
|
||
<div class="title"><slot name="header">标题</slot></div>
|
||
<div class="closeBar"></div>
|
||
</header>
|
||
<div class="headerLine"></div>
|
||
<main>
|
||
<div>
|
||
<slot name="default"/>
|
||
</div>
|
||
</main>
|
||
<footer>
|
||
<div class="button">
|
||
<NiSpace>
|
||
<slot name="footer"/>
|
||
</NiSpace>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
</teleport>
|
||
</template>
|
||
|
||
<style scoped lang="scss">
|
||
.NiPopup-enter-active, .NiPopup-leave-active, .NiPopup-enter-active .NiPopupContainer, .NiPopup-leave-active .NiPopupContainer{
|
||
transition: all .3s;
|
||
}
|
||
.NiPopup-enter-from, .NiPopup-leave-to {
|
||
opacity: 0;
|
||
background: rgba(0, 0, 0, 0);
|
||
}
|
||
.NiPopup-enter-from .NiPopupContainer, .NiPopup-leave-to .NiPopupContainer{
|
||
transform: scale(0);
|
||
}
|
||
.NiPopup {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
background: rgba(0, 0, 0, 0.2);
|
||
z-index: 999;
|
||
}
|
||
|
||
// 弹窗主体
|
||
.NiPopupContainer {
|
||
position: relative;
|
||
will-change: transform; /* 提前告知浏览器会变化的属性 will-change: transform, opacity; 优化性能*/
|
||
--width: 600px;
|
||
max-width: 80vw;
|
||
max-height: 80vh;
|
||
width: var(--width);
|
||
border-radius: var(--Ni-border-radius);
|
||
box-shadow: var(--Ni-box-shadow);
|
||
border: none;
|
||
transform-origin: center; /* 设置缩放中心点 */
|
||
background-color: var(--Ni-content-bg-color);
|
||
display: flex;
|
||
flex-direction: column;
|
||
z-index: 999;
|
||
|
||
@media (min-width: 768px){
|
||
&{
|
||
aspect-ratio: 1/0.618;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 767px){
|
||
&{
|
||
height: auto;
|
||
}
|
||
}
|
||
}
|
||
// 弹窗头
|
||
.NiPopupContainer>header{
|
||
position: relative;
|
||
height: 2rem;
|
||
padding: var(--Ni-content-padding);
|
||
display: flex;
|
||
|
||
& > div.logo{
|
||
position: relative;
|
||
font-size: 0.4rem;
|
||
display: flex;
|
||
align-items: center;
|
||
height: 100%;
|
||
bottom: -0.15rem;
|
||
& > div{
|
||
height: auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
& > div.logo{
|
||
font-size: 6px !important;
|
||
}
|
||
}
|
||
|
||
& > div.title{
|
||
position: relative;
|
||
height: 100%;
|
||
padding-left: .7rem;
|
||
display: flex;
|
||
align-items: center;
|
||
user-select: none;
|
||
color: var(--Ni-theme-color);
|
||
margin-left: .7rem;
|
||
font-size: 1.2rem;
|
||
|
||
&:before{
|
||
content: ' ';
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 30%;
|
||
top: 42%;
|
||
left: 0;
|
||
background-color: var(--Ni-theme-color-T1);
|
||
}
|
||
}
|
||
}
|
||
// 弹窗内容头分割线
|
||
.NiPopupContainer>div.headerLine{
|
||
height: 3px;
|
||
width: 100%;
|
||
background-color: var(--Ni-theme-color-T0-10);
|
||
}
|
||
// 弹窗内容
|
||
.NiPopupContainer > main{
|
||
position: relative;
|
||
flex: 1;
|
||
padding: var(--Ni-content-padding);
|
||
overflow: hidden;
|
||
& > div{
|
||
position: relative;
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
}
|
||
|
||
// 弹窗页脚
|
||
.NiPopupContainer > footer{
|
||
position: relative;
|
||
padding: var(--Ni-content-padding);
|
||
min-height: 2rem;
|
||
display: flex;
|
||
|
||
& > div.button{
|
||
position: relative;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row-reverse;
|
||
}
|
||
|
||
}
|
||
|
||
/* 不用了*/
|
||
.NiPopupContainer.onLeave{
|
||
animation: scaleUp 0.5s ease-in-out;
|
||
animation-fill-mode: forwards; /* 保持动画结束状态 */
|
||
animation-direction: reverse;/* 反向播放时 */
|
||
}
|
||
|
||
|
||
</style> |