refactor(pages): 使用 flex 布局替代动态计算滚动高度
parent
203fd7a1a6
commit
b3fc83af1b
|
|
@ -27,8 +27,8 @@
|
|||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 - 独立滚动 -->
|
||||
<scroll-view class="detail-scroll" scroll-y :style="{ height: scrollViewHeight + 'px' }">
|
||||
<!-- 内容区域 - 滚动 -->
|
||||
<scroll-view class="detail-scroll" scroll-y>
|
||||
<view class="detail-content">
|
||||
<!-- 基本信息 -->
|
||||
<template v-if="currentTab === 'basic'">
|
||||
|
|
@ -119,15 +119,12 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
// 当前Tab
|
||||
const currentTab = ref('basic');
|
||||
|
||||
// 列表滚动区域高度(单位:px)
|
||||
const scrollViewHeight = ref(0);
|
||||
|
||||
// 活动详情数据
|
||||
const activityInfo = ref({
|
||||
id: null,
|
||||
|
|
@ -152,24 +149,6 @@ onLoad((options) => {
|
|||
}
|
||||
});
|
||||
|
||||
// 渲染完成后计算scroll-view高度
|
||||
onMounted(() => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
uni.createSelectorQuery()
|
||||
.in(instance)
|
||||
.select('.tab-bar')
|
||||
.boundingClientRect((rect) => {
|
||||
if (rect) {
|
||||
// scroll-view高度 = 屏幕可用高度 - tab栏高度 - tab栏距顶部距离 - 底部按钮高度(120rpx)
|
||||
scrollViewHeight.value = sysInfo.windowHeight - rect.height - rect.top - 60;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
});
|
||||
|
||||
// 加载活动详情
|
||||
async function loadActivityDetail(id) {
|
||||
// TODO: 调用API获取活动详情
|
||||
|
|
@ -224,16 +203,20 @@ function goBack() {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面容器 */
|
||||
/* 页面容器:纵向 flex 布局,减去导航栏高度 */
|
||||
.activity-detail-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 176rpx);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部Banner轮播 */
|
||||
.banner-swiper {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.banner-img {
|
||||
|
|
@ -249,6 +232,7 @@ function goBack() {
|
|||
background-color: #FFFFFF;
|
||||
padding: 0 32rpx;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
flex-shrink: 0;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
|
|
@ -284,9 +268,12 @@ function goBack() {
|
|||
}
|
||||
}
|
||||
|
||||
/* 内容滚动区域 */
|
||||
/* 内容滚动区域:flex:1 占满剩余空间,height:0 是小程序 scroll-view 配合 flex 的关键 */
|
||||
.detail-scroll {
|
||||
/* 高度由JS动态绑定 */
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 详情内容 */
|
||||
|
|
@ -410,9 +397,9 @@ function goBack() {
|
|||
padding: 32rpx;
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
/* 底部占位:基础间距 + 安全区域 */
|
||||
.bottom-placeholder {
|
||||
height: 140rpx;
|
||||
height: calc(140rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 底部固定按钮栏 */
|
||||
|
|
|
|||
|
|
@ -5,41 +5,41 @@
|
|||
<!-- 渐变背景 -->
|
||||
<view class="gradient-bg"></view>
|
||||
|
||||
<!-- 固定头部区域(标题+发布信息) -->
|
||||
<view class="detail-header">
|
||||
<view class="header-inner">
|
||||
<!-- 标题 -->
|
||||
<view class="detail-title">
|
||||
<text class="title-text">{{ detailInfo.title }}</text>
|
||||
</view>
|
||||
<!-- 头部区域(标题+发布信息) -->
|
||||
<view class="detail-header">
|
||||
<view class="header-inner">
|
||||
<!-- 标题 -->
|
||||
<view class="detail-title">
|
||||
<text class="title-text">{{ detailInfo.title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发布信息 -->
|
||||
<view class="publish-info">
|
||||
<text class="community-name">{{ detailInfo.viewCount || 0 }}次浏览</text>
|
||||
<text class="publish-date">{{ detailInfo.publishDate ? sheep.$helper.timeFormat(detailInfo.publishDate, 'yyyy年mm月dd日 hh:MM') : '' }}</text>
|
||||
</view>
|
||||
<!-- 发布信息 -->
|
||||
<view class="publish-info">
|
||||
<text class="community-name">{{ detailInfo.viewCount || 0 }}次浏览</text>
|
||||
<text class="publish-date">{{ detailInfo.publishDate ? sheep.$helper.timeFormat(detailInfo.publishDate, 'yyyy年mm月dd日 hh:MM') : '' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<view class="divider"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 富文本内容 - 固定滚动区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y :style="{ height: scrollHeight + 'px', top: scrollTop + 'px' }">
|
||||
<view class="content-card">
|
||||
<view class="content-inner">
|
||||
<mp-html :content="detailInfo.content"></mp-html>
|
||||
<!-- 分割线 -->
|
||||
<view class="divider"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部占位 -->
|
||||
<view class="bottom-placeholder"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 富文本内容 - 滚动区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-card">
|
||||
<view class="content-inner">
|
||||
<mp-html :content="detailInfo.content"></mp-html>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部占位 -->
|
||||
<view class="bottom-placeholder"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import KnowledgeApi from '@/sheep/api/community/knowledge';
|
||||
import sheep from '@/sheep';
|
||||
|
|
@ -52,11 +52,6 @@ const detailInfo = ref({
|
|||
content: '',
|
||||
});
|
||||
|
||||
// 滚动区域高度
|
||||
const scrollHeight = ref(0);
|
||||
// 滚动区域距离顶部距离
|
||||
const scrollTop = ref(0);
|
||||
|
||||
// 页面加载
|
||||
onLoad((options) => {
|
||||
if (options.id) {
|
||||
|
|
@ -64,30 +59,6 @@ onLoad((options) => {
|
|||
}
|
||||
});
|
||||
|
||||
// 计算scroll-view高度和位置
|
||||
const calcScrollHeight = () => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
const query = uni.createSelectorQuery().in(instance);
|
||||
|
||||
// 获取头部区域高度和位置
|
||||
query.select('.detail-header').boundingClientRect();
|
||||
query.exec((res) => {
|
||||
const headerRect = res[0];
|
||||
if (headerRect) {
|
||||
const safeBottom = sysInfo.safeAreaInsets?.bottom || 0;
|
||||
scrollTop.value = headerRect.height + headerRect.top;
|
||||
scrollHeight.value = sysInfo.windowHeight - scrollTop.value - safeBottom;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
calcScrollHeight();
|
||||
});
|
||||
|
||||
// 加载详情
|
||||
async function loadDetail(id) {
|
||||
const { code, data } = await KnowledgeApi.getDetail(id);
|
||||
|
|
@ -98,17 +69,18 @@ async function loadDetail(id) {
|
|||
publishDate: data.createTime || '',
|
||||
content: data.content || '',
|
||||
};
|
||||
|
||||
setTimeout(calcScrollHeight, 100);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面容器 */
|
||||
/* 页面容器:纵向 flex 布局,减去导航栏高度 */
|
||||
.detail-page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 176rpx);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 渐变背景 */
|
||||
|
|
@ -123,13 +95,11 @@ async function loadDetail(id) {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 固定头部区域 */
|
||||
/* 头部区域:正常文档流 */
|
||||
.detail-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
|
|
@ -177,12 +147,12 @@ async function loadDetail(id) {
|
|||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
/* 内容滚动区域 - 固定定位 */
|
||||
/* 内容滚动区域:flex:1 占满剩余空间,height:0 是小程序 scroll-view 配合 flex 的关键 */
|
||||
.content-scroll {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 内容卡片 */
|
||||
|
|
@ -209,8 +179,8 @@ async function loadDetail(id) {
|
|||
}
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
/* 底部占位:基础间距 + 安全区域 */
|
||||
.bottom-placeholder {
|
||||
height: 40rpx;
|
||||
height: calc(40rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -5,71 +5,66 @@
|
|||
<!-- 渐变背景 -->
|
||||
<view class="gradient-bg"></view>
|
||||
|
||||
<!-- 固定头部区域 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-inner">
|
||||
<!-- 文章标题 -->
|
||||
<view class="article-title">
|
||||
<text class="title-text">{{ noticeInfo.title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 发布信息 -->
|
||||
<view class="publish-info">
|
||||
<view class="publisher">
|
||||
<image class="publisher-avatar" src="/static/img/person.png" mode="aspectFill" />
|
||||
<text class="publisher-name">{{ noticeInfo.publisher }}</text>
|
||||
<!-- 头部区域 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-inner">
|
||||
<!-- 文章标题 -->
|
||||
<view class="article-title">
|
||||
<text class="title-text">{{ noticeInfo.title }}</text>
|
||||
</view>
|
||||
<text class="publish-date">{{ noticeInfo.publishDate ? sheep.$helper.timeFormat(noticeInfo.publishDate, 'yyyy/mm/dd hh:MM:ss') : '' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<view class="divider"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 富文本内容 - 独立滚动区域 -->
|
||||
<scroll-view class="article-scroll" scroll-y :style="{ height: contentScrollHeight + 'px', top: contentScrollTop + 'px' }">
|
||||
<view class="article-content">
|
||||
<view class="article-inner">
|
||||
<mp-html :content="noticeInfo.content"></mp-html>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 占位,防止内容被底部附件遮挡 -->
|
||||
<view class="bottom-placeholder" v-if="noticeInfo.attachmentList && noticeInfo.attachmentList.length > 0"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 附件列表 - 动态高度,随内容撑开 -->
|
||||
<view class="attachment-section" v-if="noticeInfo.attachmentList && noticeInfo.attachmentList.length > 0">
|
||||
<view class="attachment-inner">
|
||||
<view
|
||||
class="attachment-item"
|
||||
v-for="(item, index) in noticeInfo.attachmentList"
|
||||
:key="index"
|
||||
@tap="downloadAttachment(item)"
|
||||
>
|
||||
<view class="attachment-left">
|
||||
<image class="attachment-icon" :src="getAttachmentIcon(item.type)" mode="aspectFit" />
|
||||
<text class="attachment-name">{{ item.name }}</text>
|
||||
<!-- 发布信息 -->
|
||||
<view class="publish-info">
|
||||
<view class="publisher">
|
||||
<image class="publisher-avatar" src="/static/img/person.png" mode="aspectFill" />
|
||||
<text class="publisher-name">{{ noticeInfo.publisher }}</text>
|
||||
</view>
|
||||
<text class="publish-date">{{ noticeInfo.publishDate ? sheep.$helper.timeFormat(noticeInfo.publishDate, 'yyyy/mm/dd hh:MM:ss') : '' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<view class="divider"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 富文本内容 - 滚动区域 -->
|
||||
<scroll-view class="article-scroll" scroll-y>
|
||||
<view class="article-content">
|
||||
<view class="article-inner">
|
||||
<mp-html :content="noticeInfo.content"></mp-html>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 占位,防止内容被底部附件遮挡 -->
|
||||
<view class="bottom-placeholder" v-if="noticeInfo.attachmentList && noticeInfo.attachmentList.length > 0"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 附件列表 -->
|
||||
<view class="attachment-section" v-if="noticeInfo.attachmentList && noticeInfo.attachmentList.length > 0">
|
||||
<view class="attachment-inner">
|
||||
<view
|
||||
class="attachment-item"
|
||||
v-for="(item, index) in noticeInfo.attachmentList"
|
||||
:key="index"
|
||||
@tap="downloadAttachment(item)"
|
||||
>
|
||||
<view class="attachment-left">
|
||||
<image class="attachment-icon" :src="getAttachmentIcon(item.type)" mode="aspectFit" />
|
||||
<text class="attachment-name">{{ item.name }}</text>
|
||||
</view>
|
||||
<image class="download-icon" src="/static/img/right-icon.png" mode="aspectFit" />
|
||||
</view>
|
||||
<image class="download-icon" src="/static/img/right-icon.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import NoticeApi from '@/sheep/api/community/notice';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 富文本滚动区域高度(单位:px)
|
||||
const contentScrollHeight = ref(0);
|
||||
// 富文本滚动区域距离顶部的距离(单位:px)
|
||||
const contentScrollTop = ref(0);
|
||||
|
||||
// 公告信息
|
||||
const noticeInfo = ref({
|
||||
title: '',
|
||||
|
|
@ -86,48 +81,6 @@ onLoad((options) => {
|
|||
}
|
||||
});
|
||||
|
||||
// 计算scroll-view高度和位置
|
||||
const calcScrollHeight = () => {
|
||||
const instance = getCurrentInstance();
|
||||
nextTick(() => {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
const query = uni.createSelectorQuery().in(instance);
|
||||
|
||||
// 获取头部高度和位置
|
||||
query.select('.detail-header').boundingClientRect();
|
||||
// 获取附件区域高度(如果存在)
|
||||
query.select('.attachment-section').boundingClientRect();
|
||||
|
||||
query.exec((res) => {
|
||||
const headerRect = res[0];
|
||||
const attachmentRect = res[1];
|
||||
|
||||
let attachmentHeight = 0;
|
||||
if (attachmentRect && attachmentRect.height > 0) {
|
||||
attachmentHeight = attachmentRect.height;
|
||||
}
|
||||
|
||||
if (headerRect) {
|
||||
// scroll-view距离顶部 = 头部高度 + 头部距离顶部距离(已包含状态栏和导航栏高度)
|
||||
contentScrollTop.value = headerRect.height + headerRect.top;
|
||||
// scroll-view高度 = 屏幕可用高度 - scroll-view距离顶部 - 附件高度 - 底部安全区
|
||||
const safeBottom = sysInfo.safeAreaInsets?.bottom || 0;
|
||||
contentScrollHeight.value = sysInfo.windowHeight - contentScrollTop.value - attachmentHeight - safeBottom;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 渲染完成后计算
|
||||
onMounted(() => {
|
||||
calcScrollHeight();
|
||||
});
|
||||
|
||||
// 数据加载完成后重新计算(附件异步加载)
|
||||
watch(() => noticeInfo.value.attachmentList, () => {
|
||||
setTimeout(calcScrollHeight, 100);
|
||||
}, { deep: true });
|
||||
|
||||
// 加载公告详情
|
||||
async function loadNoticeDetail(id) {
|
||||
const { code, data } = await NoticeApi.getDetail(id);
|
||||
|
|
@ -143,8 +96,6 @@ async function loadNoticeDetail(id) {
|
|||
type: getFileType(item.name),
|
||||
})),
|
||||
};
|
||||
// 数据加载后重新计算scroll-view高度
|
||||
setTimeout(calcScrollHeight, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,10 +199,13 @@ function downloadAttachment(item) {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 页面容器 */
|
||||
/* 页面容器:纵向 flex 布局,减去导航栏高度 */
|
||||
.detail-page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 176rpx);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 渐变背景 */
|
||||
|
|
@ -266,22 +220,20 @@ function downloadAttachment(item) {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 固定头部区域(标题+发布信息) */
|
||||
/* 头部区域:正常文档流 */
|
||||
.detail-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 750rpx;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
/* 底部占位 - 根据附件数量动态调整 */
|
||||
/* 底部占位 - 基础间距 + 安全区域 */
|
||||
.bottom-placeholder {
|
||||
height: 40rpx;
|
||||
height: calc(40rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 文章标题 */
|
||||
|
|
@ -310,7 +262,6 @@ function downloadAttachment(item) {
|
|||
.publisher-avatar {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
// border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
|
|
@ -333,25 +284,22 @@ function downloadAttachment(item) {
|
|||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
/* 富文本滚动区域 - 高度由JS动态绑定 */
|
||||
/* 富文本滚动区域:flex:1 占满剩余空间,height:0 是小程序 scroll-view 配合 flex 的关键 */
|
||||
.article-scroll {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 富文本内容 - 圆角背景卡片 */
|
||||
.article-content {
|
||||
margin: 0 32rpx;
|
||||
border-radius: 24rpx;
|
||||
// background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* 富文本内容内层 - 控制padding */
|
||||
/* 富文本内容内层 */
|
||||
.article-inner {
|
||||
// padding: 32rpx;
|
||||
|
||||
:deep(p) {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
|
|
@ -366,12 +314,9 @@ function downloadAttachment(item) {
|
|||
}
|
||||
}
|
||||
|
||||
/* 附件区域 - 动态高度,最多显示3个附件 */
|
||||
/* 附件区域 */
|
||||
.attachment-section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
max-height: 360rpx;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0rpx -8rpx 64rpx 0rpx rgba(0, 0, 0, 0.16);
|
||||
|
|
|
|||
Loading…
Reference in New Issue