2026-01-20 19:36:49 +08:00
|
|
|
<template>
|
2026-02-03 10:16:54 +08:00
|
|
|
<view class="full-page" :class="pageClass" :style="pageStyle">
|
2026-01-20 19:36:49 +08:00
|
|
|
<view v-if="hasHeader" class="page-header">
|
|
|
|
|
<slot name="header"></slot>
|
|
|
|
|
</view>
|
2026-02-03 10:16:54 +08:00
|
|
|
<view class="page-main" :class="mainClass" :style="mainStyle">
|
2026-01-20 19:36:49 +08:00
|
|
|
<view v-if="customScroll" class="page-scroll">
|
|
|
|
|
<slot></slot>
|
|
|
|
|
</view>
|
2026-01-30 14:25:36 +08:00
|
|
|
<scroll-view
|
|
|
|
|
v-else
|
|
|
|
|
scroll-y="true"
|
|
|
|
|
:scroll-top="scrollTop"
|
|
|
|
|
class="page-scroll"
|
|
|
|
|
@scrolltolower="scrolltolower"
|
|
|
|
|
@scroll="onScroll"
|
|
|
|
|
>
|
2026-01-20 19:36:49 +08:00
|
|
|
<slot></slot>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="hasFooter" class="page-footer">
|
|
|
|
|
<slot name="footer"></slot>
|
|
|
|
|
</view>
|
2026-02-03 10:16:54 +08:00
|
|
|
<view v-if="showSafeArea" class="safeareaBottom"></view>
|
2026-01-20 19:36:49 +08:00
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
<script setup>
|
2026-01-30 14:25:36 +08:00
|
|
|
import { computed, useSlots, ref } from "vue";
|
|
|
|
|
import useDebounce from "@/utils/useDebounce";
|
2026-01-20 19:36:49 +08:00
|
|
|
|
2026-01-30 14:25:36 +08:00
|
|
|
const emits = defineEmits(["reachBottom"]);
|
2026-01-20 19:36:49 +08:00
|
|
|
const props = defineProps({
|
|
|
|
|
customScroll: { type: Boolean, default: false },
|
2026-02-03 10:16:54 +08:00
|
|
|
mainClass: { type: String, default: "" },
|
|
|
|
|
mainStyle: { default: '' },
|
|
|
|
|
pageClass: { type: String, default: "" },
|
|
|
|
|
pageStyle: { default: '' },
|
|
|
|
|
showSafeArea: { type: Boolean, default: true },
|
2026-01-20 19:36:49 +08:00
|
|
|
});
|
|
|
|
|
const slots = useSlots();
|
|
|
|
|
const hasHeader = computed(() => !!slots.header);
|
|
|
|
|
const hasFooter = computed(() => !!slots.footer);
|
|
|
|
|
|
|
|
|
|
const scrollTop = ref(0);
|
|
|
|
|
|
|
|
|
|
const scrolltolower = useDebounce(() => {
|
2026-01-30 14:25:36 +08:00
|
|
|
emits("reachBottom");
|
2026-01-20 19:36:49 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const onScroll = useDebounce((e) => {
|
|
|
|
|
scrollTop.value = e.detail.scrollTop;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function scrollToBottom() {
|
|
|
|
|
scrollTop.value = 999999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
2026-01-30 14:25:36 +08:00
|
|
|
scrollToBottom,
|
|
|
|
|
});
|
2026-01-20 19:36:49 +08:00
|
|
|
</script>
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.full-page {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-header,
|
|
|
|
|
.page-footer {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-main {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-scroll {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.safeareaBottom {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
height: env(safe-area-inset-bottom);
|
|
|
|
|
}
|
|
|
|
|
</style>
|