Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 1m10s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
169 lines
3.4 KiB
Vue
169 lines
3.4 KiB
Vue
<!-- eslint-disable vue/no-v-html -->
|
||
<template>
|
||
<van-popup
|
||
v-model:show="visible"
|
||
position="bottom"
|
||
:style="{ height: height }"
|
||
round
|
||
:closeable="closeable"
|
||
teleport="body"
|
||
>
|
||
<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" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 子标题/统计信息 -->
|
||
<div v-if="subtitle" class="header-stats">
|
||
<span class="stats-text" v-html="subtitle" />
|
||
<!-- 额外操作插槽 -->
|
||
<slot v-if="hasActions" name="header-actions" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内容区域(可滚动) -->
|
||
<div class="popup-scroll-content">
|
||
<slot />
|
||
</div>
|
||
|
||
<!-- 底部页脚,固定不可滚动 -->
|
||
<div v-if="slots.footer" class="popup-footer-fixed">
|
||
<slot name="footer" />
|
||
</div>
|
||
</div>
|
||
</van-popup>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, useSlots } from 'vue'
|
||
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: Boolean,
|
||
required: true
|
||
},
|
||
title: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
subtitle: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
height: {
|
||
type: String,
|
||
default: '80%'
|
||
},
|
||
closeable: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['update:modelValue'])
|
||
|
||
const slots = useSlots()
|
||
|
||
// 双向绑定
|
||
const visible = computed({
|
||
get: () => props.modelValue,
|
||
set: (value) => emit('update:modelValue', value)
|
||
})
|
||
|
||
// 判断是否有操作按钮
|
||
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);
|
||
border-bottom: 1px solid var(--van-border-color);
|
||
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;
|
||
}
|
||
|
||
.popup-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
margin: 0;
|
||
text-align: center;
|
||
color: var(--van-text-color);
|
||
/*超出长度*/
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.header-stats {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.stats-text {
|
||
margin: 0;
|
||
font-size: 14px;
|
||
color: var(--van-text-color-2);
|
||
grid-column: 2;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 按钮区域放在右侧 */
|
||
.header-stats :deep(> :last-child:not(.stats-text)) {
|
||
grid-column: 3;
|
||
justify-self: end;
|
||
}
|
||
|
||
.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);
|
||
background-color: var(--van-background-2);
|
||
padding: 12px 16px;
|
||
}
|
||
</style>
|