starwait/components/Ni/Popup.vue
2025-04-27 04:17:35 +08:00

256 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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);
}
// 弹窗主体
.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;
@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>