You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crmeb/app/pages/supply_chain/notice_detail/index.vue

409 lines
10 KiB

<template>
<view class="notice-detail-page">
<view class="content" v-if="!loading">
<view class="detail-card" v-if="notice && (notice.noticeTitle || notice.noticeId)">
<view class="title-section">
<text class="detail-title">{{ notice.noticeTitle || '未命名通知' }}</text>
</view>
<view class="info-list">
<view class="row" v-if="notice.noticeType">
<text class="label">通知类型:</text>
<text class="value">{{ dict.getLabel('sys_notice_type', notice.noticeType) || notice.noticeType }}</text>
</view>
<view class="row" v-if="notice.draftDeptName">
<text class="label">通知单位:</text>
<text class="value">{{ notice.draftDeptName }}</text>
</view>
<view class="row" v-if="notice.noticeScope">
<text class="label">通知范围:</text>
<text class="value">{{ notice.noticeScope }}</text>
</view>
<view class="row" v-if="notice.noticeTime">
<text class="label">通知时间:</text>
<text class="value">{{ notice.noticeTime }}</text>
</view>
</view>
<view class="content-box">
<jyf-parser
v-if="notice.noticeContent"
:html="notice.noticeContent"
ref="article"
:tag-style="tagStyle"
></jyf-parser>
<view class="empty" v-else>暂无通知内容</view>
</view>
<view class="attach-box" v-if="fileList.length">
<view class="attach-title">附件列表</view>
<view class="attach-list">
<view
class="attach-item"
v-for="(file, index) in fileList"
:key="file.attId || file.fileId || index"
@click="fileClick(file)"
>
<view class="left">
<image
v-if="isImageFile(file)"
class="thumb"
:src="getFileUrl(file)"
mode="aspectFill"
></image>
<view v-else class="file-icon">文</view>
<view class="meta">
<text class="name">{{ file.name || '未命名附件' }}</text>
<text class="desc">
{{ (file.attType || '文件').toUpperCase() }}
<text v-if="formatFileSize(file.attSize)"> · {{ formatFileSize(file.attSize) }}</text>
</text>
</view>
</view>
<text class="preview-btn">{{ isImageFile(file) ? '预览' : '打开' }}</text>
</view>
</view>
</view>
</view>
<view class="empty-page" v-else>
<text class="iconfont icon-wushuju"></text>
<text class="text">暂无详情数据</text>
</view>
</view>
<view class="loading" v-else>
<text>加载中...</text>
</view>
</view>
</template>
<script>
import jyfParser from '@/components/jyf-parser/jyf-parser';
import { pubnoticeDetailApi, pubnoticeReadApi } from '@/api/pubnotice.js';
import { HTTP_ADMIN_URL } from '@/config/app';
export default {
components: {
'jyf-parser': jyfParser,
},
dicts: ['sys_notice_type'],
data() {
return {
HTTP_ADMIN_URL,
loading: false,
noticeId: '',
notice: {},
tagStyle: {
img: 'max-width:100%;height:auto;display:block;',
table: 'width:100%;border-collapse:collapse;',
video: 'max-width:100%;',
},
};
},
computed: {
fileList() {
return Array.isArray(this.notice?.files) ? this.notice.files : [];
},
imageFiles() {
return this.fileList
.filter((item) => this.isImageFile(item))
.map((item) => this.getFileUrl(item))
.filter(Boolean);
},
},
onLoad(options) {
this.noticeId = options.noticeId || options.id || '';
if (this.noticeId) {
// 进入详情页后再请求:将该通知设置为已读
this.markAsRead();
this.fetchDetail();
}
},
methods: {
async markAsRead() {
if (!this.noticeId) return;
try {
await pubnoticeReadApi(this.noticeId);
} catch (e) {
// 标记已读失败不影响详情展示
}
},
async fetchDetail() {
this.loading = true;
try {
const res = await pubnoticeDetailApi(this.noticeId);
this.notice = res?.data || {};
} catch (e) {
uni.showToast({
title: typeof e === 'string' ? e : '获取通知详情失败',
icon: 'none',
});
} finally {
this.loading = false;
}
},
getFilePath(item) {
if (!item) return '';
return item.attDir || item.sattDir || item.url || item.filePath || '';
},
getFileUrl(item) {
const path = this.getFilePath(item);
if (!path) return '';
if (/^https?:\/\//i.test(path)) return path;
const base = String(this.HTTP_ADMIN_URL || '').replace(/\/$/, '');
return `${base}/${String(path).replace(/^\//, '')}`;
},
isImageFile(item) {
const type = String(item?.attType || '').toLowerCase();
return (
['pic', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(type)
);
},
formatFileSize(size) {
const num = Number(size);
if (!num || Number.isNaN(num)) return '';
if (num < 1024) return `${num}B`;
if (num < 1024 * 1024) return `${(num / 1024).toFixed(1)}KB`;
if (num < 1024 * 1024 * 1024) return `${(num / 1024 / 1024).toFixed(1)}MB`;
return `${(num / 1024 / 1024 / 1024).toFixed(1)}GB`;
},
async fileClick(item) {
const current = this.getFileUrl(item);
if (!current) {
uni.showToast({
title: '附件地址无效',
icon: 'none',
});
return;
}
if (this.isImageFile(item)) {
uni.previewImage({
current,
urls: this.imageFiles,
fail: (e) => {
uni.showToast({
title: e?.errMsg || '预览失败',
icon: 'none',
});
},
});
return;
}
uni.showLoading({
title: '加载中...',
});
try {
const downloadRes = await new Promise((resolve, reject) => {
uni.downloadFile({
url: current,
success: (res) => resolve(res),
fail: reject,
});
});
if (downloadRes.statusCode !== 200) {
throw new Error('加载失败');
}
await new Promise((resolve, reject) => {
uni.openDocument({
filePath: downloadRes.tempFilePath,
showMenu: true,
success: resolve,
fail: reject,
});
});
} catch (e) {
uni.showToast({
title: e?.errMsg || e?.message || '打开失败',
icon: 'none',
});
} finally {
uni.hideLoading();
}
},
},
};
</script>
<style lang="scss">
.notice-detail-page {
.content {
padding: 30rpx;
}
.detail-card {
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
padding: 24rpx 26rpx;
}
.title-section {
margin-bottom: 20rpx;
.detail-title {
font-size: 30rpx;
font-weight: 800;
color: #333;
display: block;
line-height: 1.4;
word-break: break-all;
}
}
.info-list {
.row {
display: flex;
margin-bottom: 14rpx;
.label {
width: 170rpx;
font-size: 24rpx;
color: #666;
flex-shrink: 0;
}
.value {
flex: 1;
font-size: 24rpx;
color: #333;
min-width: 0;
word-break: break-word;
}
}
.remark-value {
line-height: 1.5;
}
}
.content-box {
margin-top: 10rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f5f5f5;
}
.attach-box {
margin-top: 24rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f5f5f5;
.attach-title {
font-size: 28rpx;
font-weight: 700;
color: #333;
margin-bottom: 16rpx;
}
.attach-list {
.attach-item {
min-height: 108rpx;
border: 1rpx solid #f0f0f0;
border-radius: 10rpx;
padding: 14rpx 16rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
background: #fafafa;
.left {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
}
.thumb {
width: 72rpx;
height: 72rpx;
border-radius: 8rpx;
background-color: #f5f5f5;
margin-right: 14rpx;
flex-shrink: 0;
}
.file-icon {
width: 72rpx;
height: 72rpx;
border-radius: 8rpx;
background: #eef4ff;
color: #409eff;
font-size: 30rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
margin-right: 14rpx;
flex-shrink: 0;
}
.meta {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.name {
font-size: 24rpx;
color: #333;
line-height: 1.4;
word-break: break-all;
}
.desc {
margin-top: 6rpx;
font-size: 20rpx;
color: #999;
}
.preview-btn {
margin-left: 16rpx;
color: #409eff;
font-size: 24rpx;
flex-shrink: 0;
}
}
}
}
.empty-page {
margin-top: 120rpx;
display: flex;
flex-direction: column;
align-items: center;
color: #999;
.iconfont {
font-size: 80rpx;
margin-bottom: 16rpx;
}
.text {
font-size: 24rpx;
}
}
.empty {
font-size: 26rpx;
color: #999;
line-height: 2;
padding: 30rpx 0;
}
.loading {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 26rpx;
}
}
</style>