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/index.vue

446 lines
11 KiB

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>
<z-paging ref="paging" v-model="records" @query="queryList">
<template #top>
<!-- 搜索与筛选 -->
<view class="search-section">
<view class="search-box">
<input v-model="query.noticeTitle" type="text" placeholder="请输入通知标题" />
<view class="search-btn" @click="$refs.paging.reload">
搜索
</view>
</view>
<view class="filter-row">
<text class="filter-label">通知类型</text>
<picker
mode="selector"
:range="noticeTypeOptions"
range-key="label"
:value="selectedNoticeTypeIndex"
@change="onNoticeTypeChange"
>
<view class="filter-picker">
<text class="picker-text">{{ selectedNoticeTypeLabel }}</text>
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<view class="filter-row" style="margin-top: 20rpx;">
<text class="filter-label">阅读状态</text>
<picker
mode="selector"
:range="noticeReadOptions"
range-key="label"
:value="selectedNoticeReadIndex"
@change="onNoticeReadChange"
>
<view class="filter-picker">
<text class="picker-text">{{ selectedNoticeReadLabel }}</text>
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
</view>
</template>
<!-- 列表 -->
<view
v-for="item in records"
:key="item.noticeId"
class="record-card"
@click="openDetail(item.noticeId)"
>
<view class="card-inner">
<view class="card-header">
<view class="left">
<view class="title-row">
<text class="type line1">{{ item.noticeTitle || '未命名通知' }}</text>
</view>
</view>
<view class="time-wrap">
<text class="time">{{ item.noticeTime || '' }}</text>
<view class="read-badge" v-if="isUnread(item.isRead)">
<view class="red-dot"></view>
</view>
</view>
</view>
<view class="card-body">
<view class="row">
<text class="label">通知类型:</text>
<text class="value">{{ dict.getLabel('sys_notice_type', item.noticeType) || item.noticeType || '—' }}</text>
</view>
<view class="row">
<text class="label">通知范围:</text>
<text class="value">{{ item.noticeScope || '—' }}</text>
</view>
<view class="row">
<text class="label">通知单位:</text>
<text class="value">{{ item.draftDeptName || '—' }}</text>
</view>
<view class="preview" v-if="item.noticeContent">
{{ contentPreview(item.noticeContent) }}
</view>
</view>
<view class="card-footer">
<view class="detail-btn" @click.stop="openDetail(item.noticeId)">查看详情</view>
</view>
</view>
</view>
</z-paging>
</template>
<script>
import { pubnoticeListApi } from '@/api/pubnotice.js';
export default {
dicts: ['sys_notice_type'],
data() {
return {
query: {
noticeTitle: '',
noticeType: '',
// 1=已读0=未读,''=全部
isRead: '',
},
records: [],
};
},
onLoad() {
this.$refs.paging.reload();
},
onPullDownRefresh() {
this.$refs.paging.reload();
},
computed: {
noticeTypeOptions() {
const dictList = this.dict.get('sys_notice_type') || [];
const options = dictList.map(item => ({
label: item.dictLabel,
value: item.dictValue,
}));
return [{ label: '全部', value: '' }].concat(options);
},
selectedNoticeTypeIndex() {
const index = this.noticeTypeOptions.findIndex(item => item.value === this.query.noticeType);
return index >= 0 ? index : 0;
},
selectedNoticeTypeLabel() {
const current = this.noticeTypeOptions[this.selectedNoticeTypeIndex];
return current ? current.label : '全部';
},
noticeReadOptions() {
// 后端约定:空=全部0=未读1=已读
return [
{ label: '全部', value: '' },
{ label: '未读', value: 0 },
{ label: '已读', value: 1 },
];
},
selectedNoticeReadIndex() {
const index = this.noticeReadOptions.findIndex(item => item.value === this.query.isRead);
return index >= 0 ? index : 0;
},
selectedNoticeReadLabel() {
const current = this.noticeReadOptions[this.selectedNoticeReadIndex];
return current ? current.label : '全部';
},
},
methods: {
onNoticeTypeChange(e) {
const index = Number(e?.detail?.value);
const current = this.noticeTypeOptions[index];
this.query.noticeType = current ? current.value : '';
this.$refs.paging.reload();
},
onNoticeReadChange(e) {
const index = Number(e?.detail?.value);
const current = this.noticeReadOptions[index];
this.query.isRead = current ? current.value : '';
this.$refs.paging.reload();
},
buildParams(pageNo, pageSize) {
const params = {
page: pageNo,
limit: pageSize,
delFlag: '1',
};
const noticeTitle = (this.query.noticeTitle || '').trim();
if (noticeTitle) params.noticeTitle = noticeTitle;
if (this.query.noticeType) {
params.noticeType = this.query.noticeType;
// 兼容后端仍使用 type 字段筛选的场景
params.type = this.query.noticeType;
}
if (this.query.isRead !== '' && this.query.isRead !== undefined && this.query.isRead !== null) {
params.isRead = this.query.isRead;
}
return params;
},
// 获取通知列表
queryList(pageNo, pageSize) {
const params = this.buildParams(pageNo, pageSize);
pubnoticeListApi(params).then((res) => {
const data = res?.data || {};
const list = data?.list || [];
// 将请求结果通过complete传给z-paging处理
this.$refs.paging.complete(list);
// 停止下拉刷新
uni.stopPullDownRefresh && uni.stopPullDownRefresh();
}).catch((err) => {
console.error('获取通知公告失败:', err);
// 如果请求失败调用complete(false)
this.$refs.paging.complete(false);
// 停止下拉刷新
uni.stopPullDownRefresh && uni.stopPullDownRefresh();
});
},
openDetail(noticeId) {
if (!noticeId) return;
// 点击列表进入详情:只更新列表本地状态(乐观展示)
const current = this.records?.find((r) => r.noticeId === noticeId);
if (current) {
// Vue2 响应式兜底:属性不存在时用 $set
if (Object.prototype.hasOwnProperty.call(current, 'isRead')) {
current.isRead = 1;
} else if (this.$set) {
this.$set(current, 'isRead', 1);
} else {
current.isRead = 1;
}
}
uni.navigateTo({
url: `/pages/supply_chain/notice_detail/index?noticeId=${noticeId}`,
});
},
contentPreview(content) {
if (content === undefined || content === null) return '';
const s = String(content)
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
if (!s) return '';
return s.length > 80 ? s.slice(0, 80) + '...' : s;
},
getReadStatusText(isRead) {
if (isRead === 1 || isRead === '1') return '已读';
if (isRead === 0 || isRead === '0') return '未读';
return '';
},
getReadStatusClass(isRead) {
if (isRead === 1 || isRead === '1') return 'read-status--read';
if (isRead === 0 || isRead === '0') return 'read-status--unread';
return '';
},
isUnread(isRead) {
return isRead === 0 || isRead === '0';
},
},
};
</script>
<style lang="scss">
.search-section {
padding: 30rpx;
margin-bottom: 20rpx;
.search-box {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
padding: 14rpx 16rpx;
margin-bottom: 20rpx;
input {
flex: 1;
height: 64rpx;
font-size: 26rpx;
color: #333;
}
.search-btn {
height: 64rpx;
padding: 0 18rpx;
background-color: #409eff;
color: #fff;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: 600;
}
}
.filter-row {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
padding: 0 20rpx;
height: 72rpx;
.filter-label {
font-size: 26rpx;
color: #666;
}
.filter-picker {
display: flex;
align-items: center;
max-width: 420rpx;
.picker-text {
font-size: 26rpx;
color: #409eff;
max-width: 360rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.picker-arrow {
margin-left: 10rpx;
font-size: 18rpx;
color: #999;
}
}
}
}
.record-card {
padding: 0 30rpx;
margin-bottom: 20rpx;
.card-inner {
background-color: #fff;
border-radius: 10rpx;
padding: 24rpx 26rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
position: relative;
.card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 16rpx;
.left {
flex: 1;
min-width: 0;
}
.title-row {
margin-bottom: 10rpx;
}
.type {
font-size: 28rpx;
font-weight: 700;
color: #333;
max-width: 520rpx;
}
.time {
font-size: 22rpx;
color: #999;
white-space: nowrap;
}
.time-wrap {
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
flex-shrink: 0;
align-self: center;
}
}
//
.read-badge {
display: flex;
align-items: center;
justify-content: center;
margin-left: 12rpx;
.red-dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background-color: #f56c6c;
}
}
.card-body {
.row {
display: flex;
margin-bottom: 10rpx;
.label {
width: 170rpx;
font-size: 24rpx;
color: #666;
}
.value {
flex: 1;
font-size: 24rpx;
color: #333;
min-width: 0;
}
}
.preview {
margin-top: 16rpx;
font-size: 24rpx;
color: #999;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.card-footer {
margin-top: 20rpx;
display: flex;
justify-content: flex-end;
.detail-btn {
padding: 8rpx 16rpx;
background-color: #409eff;
color: #fff;
border-radius: 16rpx;
font-size: 20rpx;
}
}
}
}
.line1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>