2026-04-22 20:57:53 +08:00
|
|
|
|
<!-- 通知公告详情页 -->
|
|
|
|
|
|
<template>
|
2026-04-22 23:49:41 +08:00
|
|
|
|
<s-layout title="通知公告" navbar="inner" color="#333333">
|
|
|
|
|
|
<!-- 渐变背景 -->
|
|
|
|
|
|
<view class="gradient-bg"></view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 固定头部区域 -->
|
|
|
|
|
|
<view class="detail-header">
|
|
|
|
|
|
<view class="header-inner">
|
2026-04-22 20:57:53 +08:00
|
|
|
|
<!-- 文章标题 -->
|
|
|
|
|
|
<view class="article-title">
|
|
|
|
|
|
<text class="title-text">{{ noticeInfo.title }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 发布信息 -->
|
|
|
|
|
|
<view class="publish-info">
|
|
|
|
|
|
<view class="publisher">
|
2026-04-25 01:02:01 +08:00
|
|
|
|
<image class="publisher-avatar" src="/static/img/person.png" mode="aspectFill" />
|
2026-04-22 20:57:53 +08:00
|
|
|
|
<text class="publisher-name">{{ noticeInfo.publisher }}</text>
|
|
|
|
|
|
</view>
|
2026-04-25 01:02:01 +08:00
|
|
|
|
<text class="publish-date">{{ noticeInfo.publishDate ? sheep.$helper.timeFormat(noticeInfo.publishDate, 'yyyy/mm/dd hh:MM:ss') : '' }}</text>
|
2026-04-22 20:57:53 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分割线 -->
|
|
|
|
|
|
<view class="divider"></view>
|
|
|
|
|
|
</view>
|
2026-04-22 23:49:41 +08:00
|
|
|
|
</view>
|
2026-04-22 20:57:53 +08:00
|
|
|
|
|
2026-04-22 23:49:41 +08:00
|
|
|
|
<!-- 富文本内容 - 独立滚动区域 -->
|
|
|
|
|
|
<scroll-view class="article-scroll" scroll-y :style="{ height: contentScrollHeight + 'px', top: contentScrollTop + 'px' }">
|
|
|
|
|
|
<view class="article-content">
|
|
|
|
|
|
<view class="article-inner">
|
2026-04-22 20:57:53 +08:00
|
|
|
|
<mp-html :content="noticeInfo.content"></mp-html>
|
|
|
|
|
|
</view>
|
2026-04-22 23:49:41 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 占位,防止内容被底部附件遮挡 -->
|
2026-04-25 01:02:01 +08:00
|
|
|
|
<view class="bottom-placeholder" v-if="noticeInfo.attachmentList && noticeInfo.attachmentList.length > 0"></view>
|
2026-04-22 23:49:41 +08:00
|
|
|
|
</scroll-view>
|
2026-04-22 20:57:53 +08:00
|
|
|
|
|
2026-04-25 01:02:01 +08:00
|
|
|
|
<!-- 附件列表 - 动态高度,随内容撑开 -->
|
|
|
|
|
|
<view class="attachment-section" v-if="noticeInfo.attachmentList && noticeInfo.attachmentList.length > 0">
|
2026-04-22 23:49:41 +08:00
|
|
|
|
<view class="attachment-inner">
|
|
|
|
|
|
<view
|
|
|
|
|
|
class="attachment-item"
|
2026-04-25 01:02:01 +08:00
|
|
|
|
v-for="(item, index) in noticeInfo.attachmentList"
|
2026-04-22 23:49:41 +08:00
|
|
|
|
: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" />
|
2026-04-22 20:57:53 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</s-layout>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-04-25 01:02:01 +08:00
|
|
|
|
import { ref, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
2026-04-22 20:57:53 +08:00
|
|
|
|
import { onLoad } from '@dcloudio/uni-app';
|
2026-04-25 01:02:01 +08:00
|
|
|
|
import NoticeApi from '@/sheep/api/community/notice';
|
2026-04-22 20:57:53 +08:00
|
|
|
|
import sheep from '@/sheep';
|
|
|
|
|
|
|
|
|
|
|
|
// 富文本滚动区域高度(单位:px)
|
|
|
|
|
|
const contentScrollHeight = ref(0);
|
2026-04-22 23:49:41 +08:00
|
|
|
|
// 富文本滚动区域距离顶部的距离(单位:px)
|
|
|
|
|
|
const contentScrollTop = ref(0);
|
2026-04-22 20:57:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 公告信息
|
|
|
|
|
|
const noticeInfo = ref({
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
publisher: '',
|
|
|
|
|
|
publishDate: '',
|
|
|
|
|
|
content: '',
|
2026-04-25 01:02:01 +08:00
|
|
|
|
attachmentList: []
|
2026-04-22 20:57:53 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载
|
|
|
|
|
|
onLoad((options) => {
|
|
|
|
|
|
if (options.id) {
|
|
|
|
|
|
loadNoticeDetail(options.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-25 01:02:01 +08:00
|
|
|
|
// 计算scroll-view高度和位置
|
|
|
|
|
|
const calcScrollHeight = () => {
|
2026-04-22 20:57:53 +08:00
|
|
|
|
const instance = getCurrentInstance();
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const sysInfo = uni.getSystemInfoSync();
|
2026-04-22 23:49:41 +08:00
|
|
|
|
const query = uni.createSelectorQuery().in(instance);
|
2026-04-25 01:02:01 +08:00
|
|
|
|
|
2026-04-22 23:49:41 +08:00
|
|
|
|
// 获取头部高度和位置
|
|
|
|
|
|
query.select('.detail-header').boundingClientRect();
|
|
|
|
|
|
// 获取附件区域高度(如果存在)
|
|
|
|
|
|
query.select('.attachment-section').boundingClientRect();
|
2026-04-25 01:02:01 +08:00
|
|
|
|
|
2026-04-22 23:49:41 +08:00
|
|
|
|
query.exec((res) => {
|
|
|
|
|
|
const headerRect = res[0];
|
|
|
|
|
|
const attachmentRect = res[1];
|
2026-04-25 01:02:01 +08:00
|
|
|
|
|
2026-04-22 23:49:41 +08:00
|
|
|
|
let attachmentHeight = 0;
|
|
|
|
|
|
if (attachmentRect && attachmentRect.height > 0) {
|
|
|
|
|
|
attachmentHeight = attachmentRect.height;
|
|
|
|
|
|
}
|
2026-04-25 01:02:01 +08:00
|
|
|
|
|
2026-04-22 23:49:41 +08:00
|
|
|
|
if (headerRect) {
|
|
|
|
|
|
// scroll-view距离顶部 = 头部高度 + 头部距离顶部距离(已包含状态栏和导航栏高度)
|
|
|
|
|
|
contentScrollTop.value = headerRect.height + headerRect.top;
|
2026-04-25 01:02:01 +08:00
|
|
|
|
// scroll-view高度 = 屏幕可用高度 - scroll-view距离顶部 - 附件高度 - 底部安全区
|
|
|
|
|
|
const safeBottom = sysInfo.safeAreaInsets?.bottom || 0;
|
|
|
|
|
|
contentScrollHeight.value = sysInfo.windowHeight - contentScrollTop.value - attachmentHeight - safeBottom;
|
2026-04-22 23:49:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-22 20:57:53 +08:00
|
|
|
|
});
|
2026-04-25 01:02:01 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染完成后计算
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
calcScrollHeight();
|
2026-04-22 20:57:53 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-25 01:02:01 +08:00
|
|
|
|
// 数据加载完成后重新计算(附件异步加载)
|
|
|
|
|
|
watch(() => noticeInfo.value.attachmentList, () => {
|
|
|
|
|
|
setTimeout(calcScrollHeight, 100);
|
|
|
|
|
|
}, { deep: true });
|
|
|
|
|
|
|
2026-04-22 20:57:53 +08:00
|
|
|
|
// 加载公告详情
|
|
|
|
|
|
async function loadNoticeDetail(id) {
|
2026-04-25 01:02:01 +08:00
|
|
|
|
const { code, data } = await NoticeApi.getDetail(id);
|
|
|
|
|
|
if (code === 0 && data) {
|
|
|
|
|
|
noticeInfo.value = {
|
|
|
|
|
|
title: data.title || '',
|
|
|
|
|
|
publisher: data.publisher || '',
|
|
|
|
|
|
publishDate: data.publishTime || '',
|
|
|
|
|
|
content: data.content || '',
|
|
|
|
|
|
attachmentList: (data.attachmentList || []).map((item) => ({
|
|
|
|
|
|
name: item.name,
|
|
|
|
|
|
url: item.url,
|
|
|
|
|
|
type: getFileType(item.name),
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
// 数据加载后重新计算scroll-view高度
|
|
|
|
|
|
setTimeout(calcScrollHeight, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据文件名判断文件类型
|
|
|
|
|
|
function getFileType(fileName) {
|
|
|
|
|
|
if (!fileName) return 'file';
|
|
|
|
|
|
const ext = fileName.split('.').pop().toLowerCase();
|
|
|
|
|
|
const typeMap = {
|
|
|
|
|
|
png: 'image',
|
|
|
|
|
|
jpg: 'image',
|
|
|
|
|
|
jpeg: 'image',
|
|
|
|
|
|
gif: 'image',
|
|
|
|
|
|
xls: 'excel',
|
|
|
|
|
|
xlsx: 'excel',
|
|
|
|
|
|
pdf: 'pdf',
|
|
|
|
|
|
doc: 'word',
|
|
|
|
|
|
docx: 'word',
|
2026-04-22 20:57:53 +08:00
|
|
|
|
};
|
2026-04-25 01:02:01 +08:00
|
|
|
|
return typeMap[ext] || 'file';
|
2026-04-22 20:57:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取附件图标
|
|
|
|
|
|
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>
|
2026-04-22 23:49:41 +08:00
|
|
|
|
/* 渐变背景 */
|
|
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 页面容器 */
|
2026-04-22 20:57:53 +08:00
|
|
|
|
.notice-detail-page {
|
|
|
|
|
|
position: relative;
|
2026-04-22 23:49:41 +08:00
|
|
|
|
z-index: 1;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 固定头部区域(标题+发布信息) */
|
|
|
|
|
|
.detail-header {
|
2026-04-22 23:49:41 +08:00
|
|
|
|
position: fixed;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 750rpx;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-inner {
|
|
|
|
|
|
padding: 0 32rpx;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 01:02:01 +08:00
|
|
|
|
/* 底部占位 - 根据附件数量动态调整 */
|
2026-04-22 20:57:53 +08:00
|
|
|
|
.bottom-placeholder {
|
2026-04-25 01:02:01 +08:00
|
|
|
|
height: 40rpx;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 文章标题 */
|
|
|
|
|
|
.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;
|
2026-04-25 01:02:01 +08:00
|
|
|
|
// border-radius: 50%;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
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 {
|
2026-04-22 23:49:41 +08:00
|
|
|
|
position: fixed;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
z-index: 5;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 23:49:41 +08:00
|
|
|
|
/* 富文本内容 - 圆角背景卡片 */
|
2026-04-22 20:57:53 +08:00
|
|
|
|
.article-content {
|
2026-04-22 23:49:41 +08:00
|
|
|
|
margin: 0 32rpx;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
border-radius: 24rpx;
|
2026-04-22 23:49:41 +08:00
|
|
|
|
// background-color: #FFFFFF;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 富文本内容内层 - 控制padding */
|
|
|
|
|
|
.article-inner {
|
|
|
|
|
|
// padding: 32rpx;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
|
|
|
|
|
|
:deep(p) {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333333;
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(img) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
margin: 24rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 01:02:01 +08:00
|
|
|
|
/* 附件区域 - 动态高度,最多显示3个附件 */
|
2026-04-22 20:57:53 +08:00
|
|
|
|
.attachment-section {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
2026-04-22 23:49:41 +08:00
|
|
|
|
width: 100%;
|
2026-04-25 01:02:01 +08:00
|
|
|
|
max-height: 360rpx;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
background: #FFFFFF;
|
|
|
|
|
|
box-shadow: 0rpx -8rpx 64rpx 0rpx rgba(0, 0, 0, 0.16);
|
2026-04-25 01:02:01 +08:00
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
overflow-y: auto;
|
2026-04-22 23:49:41 +08:00
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.attachment-inner {
|
|
|
|
|
|
padding: 24rpx 32rpx;
|
2026-04-22 20:57:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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>
|