fjrcloud-community-app/pages/sub/notice/detail.vue

412 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 通知公告详情页 -->
<template>
<s-layout title="通知公告" navbar="inner" color="#333333">
<!-- 渐变背景 -->
<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/login_img.png" mode="aspectFill" />
<text class="publisher-name">{{ noticeInfo.publisher }}</text>
</view>
<text class="publish-date">{{ noticeInfo.publishDate }}</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.attachments && noticeInfo.attachments.length > 0"></view>
</scroll-view>
<!-- 附件列表 - 固定在底部 -->
<view class="attachment-section" v-if="noticeInfo.attachments && noticeInfo.attachments.length > 0">
<view class="attachment-inner">
<view
class="attachment-item"
v-for="(item, index) in noticeInfo.attachments"
: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>
</view>
</view>
</s-layout>
</template>
<script setup>
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import sheep from '@/sheep';
// 富文本滚动区域高度单位px
const contentScrollHeight = ref(0);
// 富文本滚动区域距离顶部的距离单位px
const contentScrollTop = ref(0);
// 公告信息
const noticeInfo = ref({
title: '',
publisher: '',
publishDate: '',
content: '',
attachments: []
});
// 页面加载
onLoad((options) => {
if (options.id) {
loadNoticeDetail(options.id);
}
});
// 渲染完成后计算scroll-view高度和位置
onMounted(() => {
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距离顶部 - 附件高度
contentScrollHeight.value = sysInfo.windowHeight - contentScrollTop.value - attachmentHeight;
}
});
});
});
// 加载公告详情
async function loadNoticeDetail(id) {
// TODO: 调用API获取公告详情
// const { code, data } = await NoticeApi.getNoticeDetail(id);
// if (code === 0) {
// noticeInfo.value = data;
// }
// 模拟数据
noticeInfo.value = {
title: '关于召开业主大会讨论物业费价格调整的通知',
publisher: 'x小区物业',
publishDate: '2026-01-20',
content: `<p>各位业主:</p>
<p>为进一步提升本小区物业服务质量,保障小区公共设施设备的正常运维、环境卫生整治、安保服务升级等工作有序开展,切实维护全体业主的共同利益,根据《物业管理条例》《业主大会和业主委员会指导规则》及本小区《管理规约》相关规定,经业主委员会研究决定,召开业主大会,专门讨论本小区物业费价格调整相关事宜。现将具体事项通知如下:</p>
<p>一、会议基本信息</p>
<p>会议时间2026年2月15日周六上午9:00</p>
<p>会议地点:小区活动中心一楼会议室</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>
<p>参会人员:全体业主或业主代表</p>`,
attachments: [
{ name: 'IMG-2309.PNG', type: 'image', url: '' },
{ name: 'EXCEL-2309.XLXS', type: 'excel', url: '' }
]
};
}
// 获取附件图标
function getAttachmentIcon(type) {
const iconMap = {
image: '/static/img/eli-icon1.png',
excel: '/static/img/eli-icon2.png',
pdf: '/static/img/eli-icon2.png',
word: '/static/img/eli-icon2.png'
};
return iconMap[type] || '/static/img/eli-icon2.png';
}
// 下载附件
function downloadAttachment(item) {
if (!item.url) {
uni.showToast({
title: '附件链接不存在',
icon: 'none'
});
return;
}
uni.showLoading({
title: '下载中...'
});
uni.downloadFile({
url: item.url,
success: (res) => {
if (res.statusCode === 200) {
// 保存到本地
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
uni.hideLoading();
uni.showToast({
title: '下载成功',
icon: 'success'
});
// 打开文件
uni.openDocument({
filePath: saveRes.savedFilePath,
showMenu: true,
success: () => {
console.log('打开文档成功');
},
fail: () => {
uni.showToast({
title: '无法打开该文件',
icon: 'none'
});
}
});
},
fail: () => {
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
},
fail: () => {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
});
}
</script>
<style lang="scss" scoped>
/* 渐变背景 */
.gradient-bg {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 660rpx;
background: linear-gradient(180deg, #F8EDE8 0%, #F5F5F5 50%);
z-index: 0;
pointer-events: none;
}
/* 页面容器 */
.notice-detail-page {
position: relative;
z-index: 1;
}
/* 固定头部区域(标题+发布信息) */
.detail-header {
position: fixed;
left: 0;
width: 750rpx;
z-index: 10;
background: transparent;
}
.header-inner {
padding: 0 32rpx;
}
/* 底部占位 */
.bottom-placeholder {
height: 300rpx;
}
/* 文章标题 */
.article-title {
padding: 32rpx 0 24rpx;
.title-text {
font-size: 36rpx;
font-weight: 600;
color: #333333;
line-height: 1.5;
}
}
/* 发布信息 */
.publish-info {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 24rpx;
.publisher {
display: flex;
align-items: center;
.publisher-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
margin-right: 16rpx;
}
.publisher-name {
font-size: 28rpx;
color: #666666;
}
}
.publish-date {
font-size: 26rpx;
color: #999999;
}
}
/* 分割线 */
.divider {
height: 1rpx;
background-color: #E5E5E5;
margin-bottom: 32rpx;
}
/* 富文本滚动区域 - 高度由JS动态绑定 */
.article-scroll {
position: fixed;
left: 0;
width: 100%;
z-index: 5;
}
/* 富文本内容 - 圆角背景卡片 */
.article-content {
margin: 0 32rpx;
border-radius: 24rpx;
// background-color: #FFFFFF;
}
/* 富文本内容内层 - 控制padding */
.article-inner {
// padding: 32rpx;
:deep(p) {
font-size: 28rpx;
color: #333333;
line-height: 1.8;
margin-bottom: 16rpx;
}
:deep(img) {
width: 100%;
border-radius: 16rpx;
margin: 24rpx 0;
}
}
/* 附件区域 - 固定在底部 */
.attachment-section {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 260rpx;
background: #FFFFFF;
box-shadow: 0rpx -8rpx 64rpx 0rpx rgba(0, 0, 0, 0.16);
border-radius: 0;
overflow-y: auto;
z-index: 9999;
}
.attachment-inner {
padding: 24rpx 32rpx;
}
.attachment-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
&:not(:last-child) {
border-bottom: 1rpx solid #F0F0F0;
}
.attachment-left {
display: flex;
align-items: center;
flex: 1;
.attachment-icon {
width: 48rpx;
height: 48rpx;
margin-right: 20rpx;
}
.attachment-name {
font-size: 28rpx;
color: #333333;
}
}
.download-icon {
width: 32rpx;
height: 32rpx;
}
}
</style>