Files
EmailBill/Web/src/components/PopupContainer.vue

167 lines
3.5 KiB
Vue
Raw Normal View History

<!-- eslint-disable vue/no-v-html -->
<template>
2025-12-30 17:02:30 +08:00
<van-popup
v-model:show="visible"
position="bottom"
:style="{ height: height }"
round
:closeable="closeable"
teleport="body"
2025-12-30 17:02:30 +08:00
>
<div class="popup-container">
<!-- 头部区域 -->
<div class="popup-header-fixed">
<!-- 标题行 (无子标题且有操作时使用 Grid 布局) -->
<div class="header-title-row" :class="{ 'has-actions': !subtitle && hasActions }">
<h3 class="popup-title">{{ title }}</h3>
<!-- 无子标题时操作按钮与标题同行 -->
<div v-if="!subtitle && hasActions" class="header-actions-inline">
<slot name="header-actions"></slot>
</div>
</div>
2025-12-30 17:02:30 +08:00
<!-- 子标题/统计信息 -->
<div v-if="subtitle" class="header-stats">
<span class="stats-text" v-html="subtitle" />
2025-12-30 17:02:30 +08:00
<!-- 额外操作插槽 -->
<slot v-if="hasActions" name="header-actions"></slot>
2025-12-30 17:02:30 +08:00
</div>
</div>
<!-- 内容区域可滚动 -->
<div class="popup-scroll-content">
<slot></slot>
</div>
<!-- 底部页脚固定不可滚动 -->
<div v-if="slots.footer" class="popup-footer-fixed">
<slot name="footer"></slot>
</div>
2025-12-30 17:02:30 +08:00
</div>
</van-popup>
</template>
<script setup>
import { computed, useSlots } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
required: true,
2025-12-30 17:02:30 +08:00
},
title: {
type: String,
default: '',
2025-12-30 17:02:30 +08:00
},
subtitle: {
type: String,
default: '',
2025-12-30 17:02:30 +08:00
},
height: {
type: String,
default: '80%',
2025-12-30 17:02:30 +08:00
},
closeable: {
type: Boolean,
default: true,
},
2025-12-30 17:02:30 +08:00
})
const emit = defineEmits(['update:modelValue'])
const slots = useSlots()
// 双向绑定
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
2025-12-30 17:02:30 +08:00
})
// 判断是否有操作按钮
const hasActions = computed(() => !!slots['header-actions'])
</script>
<style scoped>
.popup-container {
height: 100%;
display: flex;
flex-direction: column;
}
.popup-header-fixed {
flex-shrink: 0;
padding: 16px;
background-color: var(--van-background-2, #f7f8fa);
border-bottom: 1px solid var(--van-border-color, #ebedf0);
position: sticky;
top: 0;
z-index: 10;
}
.header-title-row.has-actions {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
}
.header-title-row.has-actions .popup-title {
grid-column: 2;
min-width: 0;
}
.header-actions-inline {
grid-column: 3;
justify-self: end;
display: flex;
align-items: center;
}
2025-12-30 17:02:30 +08:00
.popup-title {
font-size: 16px;
font-weight: 500;
margin: 0;
text-align: center;
color: var(--van-text-color, #323233);
2025-12-30 18:49:46 +08:00
/*超出长度*/
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
2025-12-30 17:02:30 +08:00
}
.header-stats {
2025-12-30 18:49:46 +08:00
display: grid;
grid-template-columns: 1fr auto 1fr;
2025-12-30 17:02:30 +08:00
align-items: center;
gap: 12px;
margin-top: 12px;
}
.stats-text {
margin: 0;
font-size: 14px;
color: var(--van-text-color-2, #646566);
2025-12-30 18:49:46 +08:00
grid-column: 2;
2025-12-30 17:02:30 +08:00
text-align: center;
}
2025-12-30 18:49:46 +08:00
/* 按钮区域放在右侧 */
.header-stats :deep(> :last-child:not(.stats-text)) {
grid-column: 3;
justify-self: end;
}
2025-12-30 17:02:30 +08:00
.popup-scroll-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.popup-footer-fixed {
flex-shrink: 0;
border-top: 1px solid var(--van-border-color, #ebedf0);
background-color: var(--van-background-2, #f7f8fa);
padding: 12px 16px;
}
2025-12-30 17:02:30 +08:00
</style>