修改小程序

This commit is contained in:
2025-10-17 17:29:11 +08:00
parent 434fa135d1
commit 212dffd0da
227 changed files with 12887 additions and 45341 deletions

View File

@@ -0,0 +1,326 @@
// pages/notification/detail/detail.js
Page({
data: {
statusBarHeight: 0,
loading: true,
notificationId: '',
notificationInfo: {}
},
onLoad(options) {
// 检查登录状态
this.checkLoginStatus();
// 获取状态栏高度
const systemInfo = wx.getSystemInfoSync();
this.setData({
statusBarHeight: systemInfo.statusBarHeight
});
// 获取通知ID
if (options.id) {
this.setData({
notificationId: options.id
});
this.loadNotificationDetail();
} else {
wx.showToast({
title: '参数错误',
icon: 'error'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
},
onShow() {
// 检查登录状态
this.checkLoginStatus();
},
// 检查登录状态
checkLoginStatus() {
const token = wx.getStorageSync('token');
if (!token) {
wx.redirectTo({
url: '/pages/login/login'
});
return false;
}
return true;
},
// 加载通知详情
async loadNotificationDetail() {
try {
this.setData({ loading: true });
// 模拟API调用
const notificationData = await this.fetchNotificationDetail(this.data.notificationId);
this.setData({
notificationInfo: notificationData,
loading: false
});
// 标记为已读
this.markAsRead();
} catch (error) {
console.error('加载通知详情失败:', error);
wx.showToast({
title: '加载失败',
icon: 'error'
});
this.setData({ loading: false });
}
},
// 模拟获取通知详情API
fetchNotificationDetail(notificationId) {
return new Promise((resolve) => {
setTimeout(() => {
// 根据ID生成不同类型的模拟数据
const mockData = this.generateMockNotification(notificationId);
resolve(mockData);
}, 1000);
});
},
// 生成模拟通知数据
generateMockNotification(id) {
const types = ['system', 'alert', 'task'];
const type = types[parseInt(id) % 3];
const baseData = {
id: id,
type: type,
time: this.formatTime(new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000)),
isRead: false
};
switch (type) {
case 'system':
return {
...baseData,
title: '系统维护通知',
content: '系统将于今晚22:00-24:00进行例行维护期间可能影响部分功能使用请提前做好相关准备工作。维护期间如有紧急情况请联系技术支持热线。',
attachments: [
{
id: '1',
name: '维护说明文档.pdf',
size: '2.3MB'
}
],
processRecords: [
{
id: '1',
time: '10:30',
action: '发布通知',
operator: '系统管理员',
remark: '定期系统维护通知'
}
]
};
case 'alert':
return {
...baseData,
title: '牲畜异常告警',
content: '检测到张三养殖场出现牲畜异常情况,请及时处理。系统监测显示该养殖场的牲畜活动异常,可能存在健康风险,建议立即派遣检查人员前往现场核实情况。',
alertDetails: {
level: 'high',
farmerName: '张三',
location: '西夏区兴泾镇张三养殖场',
reason: '牲畜活动异常,疑似健康问题'
},
processRecords: [
{
id: '1',
time: '14:25',
action: '系统自动告警',
operator: '监控系统',
remark: '检测到异常数据'
}
]
};
case 'task':
return {
...baseData,
title: '月度检查任务',
content: '请在本月底前完成辖区内所有养殖户的月度例行检查工作。检查内容包括:牲畜健康状况、饲料质量、环境卫生、防疫措施等。请及时提交检查报告。',
taskDetails: {
taskType: '月度例行检查',
deadline: '2024-01-31 18:00',
status: 'pending',
priority: 'medium'
},
attachments: [
{
id: '1',
name: '检查清单.xlsx',
size: '156KB'
},
{
id: '2',
name: '报告模板.docx',
size: '89KB'
}
],
processRecords: [
{
id: '1',
time: '09:00',
action: '任务分配',
operator: '管理员',
remark: '月度例行检查任务'
}
]
};
default:
return baseData;
}
},
// 格式化时间
formatTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}`;
},
// 标记为已读
async markAsRead() {
try {
// 模拟API调用
await this.markNotificationAsRead(this.data.notificationId);
console.log('通知已标记为已读');
} catch (error) {
console.error('标记已读失败:', error);
}
},
// 模拟标记已读API
markNotificationAsRead(notificationId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true });
}, 500);
});
},
// 处理告警
onHandleAlert() {
const alertDetails = this.data.notificationInfo.alertDetails;
wx.showModal({
title: '处理告警',
content: `确定要处理来自${alertDetails.farmerName}的告警吗?`,
success: (res) => {
if (res.confirm) {
// 这里可以跳转到具体的处理页面
wx.showToast({
title: '正在处理...',
icon: 'loading'
});
setTimeout(() => {
wx.showToast({
title: '处理成功',
icon: 'success'
});
}, 2000);
}
}
});
},
// 处理任务
onHandleTask() {
const taskDetails = this.data.notificationInfo.taskDetails;
wx.showModal({
title: '处理任务',
content: `确定要开始处理"${taskDetails.taskType}"任务吗?`,
success: (res) => {
if (res.confirm) {
// 这里可以跳转到具体的任务处理页面
wx.showToast({
title: '任务已接受',
icon: 'success'
});
}
}
});
},
// 分享通知
onShare() {
wx.showActionSheet({
itemList: ['复制链接', '发送给同事', '保存到本地'],
success: (res) => {
switch (res.tapIndex) {
case 0:
wx.setClipboardData({
data: `通知详情:${this.data.notificationInfo.title}`,
success: () => {
wx.showToast({
title: '已复制到剪贴板',
icon: 'success'
});
}
});
break;
case 1:
wx.showToast({
title: '功能开发中',
icon: 'none'
});
break;
case 2:
wx.showToast({
title: '保存成功',
icon: 'success'
});
break;
}
}
});
},
// 查看附件
onAttachmentTap(e) {
const attachment = e.currentTarget.dataset.attachment;
wx.showModal({
title: '查看附件',
content: `确定要查看附件"${attachment.name}"吗?`,
success: (res) => {
if (res.confirm) {
wx.showToast({
title: '正在打开...',
icon: 'loading'
});
// 这里可以实现附件预览或下载功能
setTimeout(() => {
wx.showToast({
title: '功能开发中',
icon: 'none'
});
}, 1500);
}
}
});
},
// 下拉刷新
onPullDownRefresh() {
this.loadNotificationDetail().finally(() => {
wx.stopPullDownRefresh();
});
}
});

View File

@@ -0,0 +1,7 @@
{
"navigationBarTitleText": "通知详情",
"navigationStyle": "custom",
"backgroundColor": "#f5f5f5",
"backgroundTextStyle": "dark",
"enablePullDownRefresh": true
}

View File

@@ -0,0 +1,144 @@
<!--pages/notification/detail/detail.wxml-->
<view class="container">
<!-- 状态栏 -->
<view class="status-bar" style="height: {{statusBarHeight}}px;"></view>
<!-- 加载状态 -->
<view wx:if="{{loading}}" class="loading-container">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 通知详情内容 -->
<view wx:else class="detail-content">
<!-- 通知头部 -->
<view class="notification-header">
<view class="notification-title">{{notificationInfo.title}}</view>
<view class="notification-meta">
<view class="notification-time">{{notificationInfo.time}}</view>
<view class="notification-type type-{{notificationInfo.type}}">
{{notificationInfo.type === 'system' ? '系统通知' : notificationInfo.type === 'alert' ? '告警通知' : '任务通知'}}
</view>
</view>
</view>
<!-- 通知内容 -->
<view class="notification-content">
<view class="content-section">
<view class="section-title">通知内容</view>
<view class="content-text">{{notificationInfo.content}}</view>
</view>
<!-- 告警详情 (仅告警通知显示) -->
<view wx:if="{{notificationInfo.type === 'alert' && notificationInfo.alertDetails}}" class="content-section">
<view class="section-title">告警详情</view>
<view class="alert-details">
<view class="detail-item">
<view class="detail-label">告警级别</view>
<view class="detail-value level-{{notificationInfo.alertDetails.level}}">
{{notificationInfo.alertDetails.level === 'high' ? '高级' : notificationInfo.alertDetails.level === 'medium' ? '中级' : '低级'}}
</view>
</view>
<view class="detail-item">
<view class="detail-label">涉及养殖户</view>
<view class="detail-value">{{notificationInfo.alertDetails.farmerName}}</view>
</view>
<view class="detail-item">
<view class="detail-label">告警位置</view>
<view class="detail-value">{{notificationInfo.alertDetails.location}}</view>
</view>
<view class="detail-item">
<view class="detail-label">告警原因</view>
<view class="detail-value">{{notificationInfo.alertDetails.reason}}</view>
</view>
</view>
</view>
<!-- 任务详情 (仅任务通知显示) -->
<view wx:if="{{notificationInfo.type === 'task' && notificationInfo.taskDetails}}" class="content-section">
<view class="section-title">任务详情</view>
<view class="task-details">
<view class="detail-item">
<view class="detail-label">任务类型</view>
<view class="detail-value">{{notificationInfo.taskDetails.taskType}}</view>
</view>
<view class="detail-item">
<view class="detail-label">截止时间</view>
<view class="detail-value">{{notificationInfo.taskDetails.deadline}}</view>
</view>
<view class="detail-item">
<view class="detail-label">任务状态</view>
<view class="detail-value status-{{notificationInfo.taskDetails.status}}">
{{notificationInfo.taskDetails.status === 'pending' ? '待处理' : notificationInfo.taskDetails.status === 'processing' ? '处理中' : '已完成'}}
</view>
</view>
<view class="detail-item">
<view class="detail-label">优先级</view>
<view class="detail-value priority-{{notificationInfo.taskDetails.priority}}">
{{notificationInfo.taskDetails.priority === 'high' ? '高' : notificationInfo.taskDetails.priority === 'medium' ? '中' : '低'}}
</view>
</view>
</view>
</view>
<!-- 附件信息 -->
<view wx:if="{{notificationInfo.attachments && notificationInfo.attachments.length > 0}}" class="content-section">
<view class="section-title">相关附件</view>
<view class="attachments-list">
<view
wx:for="{{notificationInfo.attachments}}"
wx:key="id"
class="attachment-item"
bindtap="onAttachmentTap"
data-attachment="{{item}}"
>
<view class="attachment-icon">📎</view>
<view class="attachment-info">
<view class="attachment-name">{{item.name}}</view>
<view class="attachment-size">{{item.size}}</view>
</view>
<view class="attachment-action">查看</view>
</view>
</view>
</view>
<!-- 处理记录 -->
<view wx:if="{{notificationInfo.processRecords && notificationInfo.processRecords.length > 0}}" class="content-section">
<view class="section-title">处理记录</view>
<view class="process-records">
<view
wx:for="{{notificationInfo.processRecords}}"
wx:key="id"
class="record-item"
>
<view class="record-time">{{item.time}}</view>
<view class="record-content">
<view class="record-action">{{item.action}}</view>
<view class="record-operator">操作人:{{item.operator}}</view>
<view wx:if="{{item.remark}}" class="record-remark">{{item.remark}}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view wx:if="{{!loading}}" class="bottom-bar">
<button
wx:if="{{notificationInfo.type === 'alert'}}"
class="action-btn handle-btn"
bindtap="onHandleAlert"
>
处理告警
</button>
<button
wx:if="{{notificationInfo.type === 'task'}}"
class="action-btn task-btn"
bindtap="onHandleTask"
>
处理任务
</button>
<button class="action-btn share-btn" bindtap="onShare">分享</button>
</view>
</view>

View File

@@ -0,0 +1,336 @@
/* pages/notification/detail/detail.wxss */
.container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.status-bar {
background-color: #2c5aa0;
}
/* 加载状态 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #2c5aa0;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 详情内容 */
.detail-content {
padding: 20rpx;
}
/* 通知头部 */
.notification-header {
background-color: white;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.notification-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
line-height: 1.4;
margin-bottom: 20rpx;
}
.notification-meta {
display: flex;
align-items: center;
justify-content: space-between;
}
.notification-time {
font-size: 26rpx;
color: #666;
}
.notification-type {
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-size: 22rpx;
font-weight: bold;
}
.type-system {
background-color: #e6f7ff;
color: #1890ff;
}
.type-alert {
background-color: #fff2f0;
color: #ff4d4f;
}
.type-task {
background-color: #fff7e6;
color: #fa8c16;
}
/* 通知内容 */
.notification-content {
background-color: white;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.content-section {
margin-bottom: 40rpx;
}
.content-section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.content-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
/* 详情项 */
.detail-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.detail-item:last-child {
border-bottom: none;
}
.detail-label {
font-size: 28rpx;
color: #666;
flex-shrink: 0;
width: 160rpx;
}
.detail-value {
font-size: 28rpx;
color: #333;
flex: 1;
text-align: right;
}
/* 告警级别 */
.level-high {
color: #ff4d4f;
font-weight: bold;
}
.level-medium {
color: #fa8c16;
font-weight: bold;
}
.level-low {
color: #52c41a;
font-weight: bold;
}
/* 任务状态 */
.status-pending {
color: #fa8c16;
font-weight: bold;
}
.status-processing {
color: #1890ff;
font-weight: bold;
}
.status-completed {
color: #52c41a;
font-weight: bold;
}
/* 优先级 */
.priority-high {
color: #ff4d4f;
font-weight: bold;
}
.priority-medium {
color: #fa8c16;
font-weight: bold;
}
.priority-low {
color: #52c41a;
font-weight: bold;
}
/* 附件列表 */
.attachments-list {
margin-top: 20rpx;
}
.attachment-item {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
margin-bottom: 12rpx;
transition: background-color 0.3s ease;
}
.attachment-item:active {
background-color: #e9ecef;
}
.attachment-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.attachment-info {
flex: 1;
}
.attachment-name {
font-size: 28rpx;
color: #333;
margin-bottom: 4rpx;
}
.attachment-size {
font-size: 24rpx;
color: #999;
}
.attachment-action {
font-size: 26rpx;
color: #2c5aa0;
}
/* 处理记录 */
.process-records {
margin-top: 20rpx;
}
.record-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.record-item:last-child {
border-bottom: none;
}
.record-time {
font-size: 24rpx;
color: #999;
width: 120rpx;
flex-shrink: 0;
}
.record-content {
flex: 1;
margin-left: 20rpx;
}
.record-action {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 8rpx;
}
.record-operator {
font-size: 24rpx;
color: #666;
margin-bottom: 4rpx;
}
.record-remark {
font-size: 26rpx;
color: #333;
line-height: 1.4;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
padding: 20rpx;
border-top: 1rpx solid #f0f0f0;
display: flex;
gap: 20rpx;
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: bold;
border: none;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.action-btn:active {
transform: scale(0.95);
}
.handle-btn {
background-color: #ff4d4f;
color: white;
}
.task-btn {
background-color: #fa8c16;
color: white;
}
.share-btn {
background-color: #2c5aa0;
color: white;
}

View File

@@ -0,0 +1,397 @@
const app = getApp();
const apiService = require('../../utils/api.js');
Page({
data: {
statusBarHeight: 0,
loading: true,
// 筛选状态
selectedType: 'all',
selectedStatus: 'all',
// 筛选选项
typeOptions: [
{ value: 'all', label: '全部类型' },
{ value: 'system', label: '系统通知' },
{ value: 'alert', label: '预警通知' },
{ value: 'task', label: '任务通知' }
],
statusOptions: [
{ value: 'all', label: '全部状态' },
{ value: 'unread', label: '未读' },
{ value: 'read', label: '已读' }
],
// 通知列表
notificationList: [],
// 分页
currentPage: 1,
pageSize: 10,
totalCount: 0,
hasMore: true,
// 统计信息
statistics: {
total: 0,
unread: 0,
system: 0,
alert: 0,
task: 0
}
},
onLoad() {
// 获取状态栏高度
const systemInfo = wx.getSystemInfoSync();
this.setData({
statusBarHeight: systemInfo.statusBarHeight
});
// 检查登录状态
this.checkLoginStatus();
},
onShow() {
// 每次显示页面时刷新数据
this.loadData();
},
// 检查登录状态
checkLoginStatus() {
const token = wx.getStorageSync('token');
if (!token) {
wx.reLaunch({
url: '/pages/login/login'
});
return false;
}
return true;
},
// 加载数据
async loadData(isRefresh = true) {
if (!this.checkLoginStatus()) return;
if (isRefresh) {
this.setData({
loading: true,
currentPage: 1,
notificationList: [],
hasMore: true
});
}
try {
await this.loadNotifications();
} catch (error) {
console.error('数据加载失败:', error);
// 加载模拟数据作为备用
this.loadMockData();
} finally {
this.setData({ loading: false });
}
},
// 加载通知列表
async loadNotifications() {
try {
const params = {
page: this.data.currentPage,
pageSize: this.data.pageSize,
type: this.data.selectedType === 'all' ? '' : this.data.selectedType,
status: this.data.selectedStatus === 'all' ? '' : this.data.selectedStatus
};
// 这里可以调用实际的通知API
// const result = await apiService.getNotifications(params);
// 暂时使用模拟数据
this.loadMockData();
} catch (error) {
console.error('获取通知列表失败:', error);
throw error;
}
},
// 加载模拟数据
loadMockData() {
const mockNotifications = [
{
id: 1,
type: 'alert',
title: '养殖户异常预警',
content: '张三养殖场检测到异常情况,请及时处理',
status: 'unread',
priority: 'high',
createTime: '2024-01-15 10:30:00',
farmerId: 1,
farmerName: '张三',
farmName: '张家养殖场'
},
{
id: 2,
type: 'system',
title: '系统维护通知',
content: '系统将于今晚22:00-24:00进行维护期间可能影响正常使用',
status: 'read',
priority: 'medium',
createTime: '2024-01-15 09:15:00'
},
{
id: 3,
type: 'task',
title: '检查任务提醒',
content: '您有一个待处理的检查任务:李氏牧场定期检查',
status: 'unread',
priority: 'medium',
createTime: '2024-01-15 08:45:00',
farmerId: 2,
farmerName: '李四',
farmName: '李氏牧场'
},
{
id: 4,
type: 'alert',
title: '设备离线预警',
content: '王家农场的监控设备已离线超过2小时',
status: 'unread',
priority: 'high',
createTime: '2024-01-14 16:20:00',
farmerId: 3,
farmerName: '王五',
farmName: '王家农场'
},
{
id: 5,
type: 'system',
title: '数据同步完成',
content: '今日数据同步已完成共同步1250条记录',
status: 'read',
priority: 'low',
createTime: '2024-01-14 15:30:00'
}
];
// 根据筛选条件过滤数据
let filteredNotifications = mockNotifications;
if (this.data.selectedType !== 'all') {
filteredNotifications = filteredNotifications.filter(n => n.type === this.data.selectedType);
}
if (this.data.selectedStatus !== 'all') {
filteredNotifications = filteredNotifications.filter(n => n.status === this.data.selectedStatus);
}
this.setData({
notificationList: filteredNotifications,
totalCount: filteredNotifications.length,
hasMore: false,
statistics: {
total: mockNotifications.length,
unread: mockNotifications.filter(n => n.status === 'unread').length,
system: mockNotifications.filter(n => n.type === 'system').length,
alert: mockNotifications.filter(n => n.type === 'alert').length,
task: mockNotifications.filter(n => n.type === 'task').length
}
});
},
// 类型筛选
onTypeChange(e) {
const type = e.currentTarget.dataset.type;
this.setData({
selectedType: type
});
this.loadData(true);
},
// 状态筛选
onStatusChange(e) {
const status = e.currentTarget.dataset.status;
this.setData({
selectedStatus: status
});
this.loadData(true);
},
// 生成模拟通知数据
generateMockNotifications() {
const types = ['system', 'alert', 'task']
const typeNames = { system: '系统通知', alert: '告警通知', task: '任务通知' }
const icons = { system: '🔔', alert: '⚠️', task: '📋' }
const notifications = []
const pageSize = this.data.pageSize
for (let i = 0; i < pageSize; i++) {
const type = types[Math.floor(Math.random() * types.length)]
const isRead = Math.random() > 0.3
const id = Date.now() + i
notifications.push({
id,
type,
typeName: typeNames[type],
icon: icons[type],
title: this.getNotificationTitle(type),
content: this.getNotificationContent(type),
time: this.formatTime(new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000)),
isRead
})
}
return notifications.filter(item => {
if (this.data.selectedTab === 'all') return true
return item.type === this.data.selectedTab
})
},
// 获取通知标题
getNotificationTitle(type) {
const titles = {
system: ['系统维护通知', '版本更新提醒', '账户安全提醒', '数据备份完成'],
alert: ['设备离线告警', '异常数据告警', '温度超标告警', '网络连接异常'],
task: ['检疫任务提醒', '报告提交截止', '数据统计任务', '设备巡检任务']
}
const typeList = titles[type] || titles.system
return typeList[Math.floor(Math.random() * typeList.length)]
},
// 获取通知内容
getNotificationContent(type) {
const contents = {
system: ['系统将于今晚进行维护预计持续2小时', '新版本已发布,请及时更新', '检测到异常登录,请注意账户安全', '数据备份已完成,请查看备份报告'],
alert: ['监控摄像头#001已离线超过30分钟', '养殖场温度传感器检测到异常数据', '养殖区域温度超过安全范围', '网络连接不稳定,请检查网络设备'],
task: ['您有新的检疫任务需要处理', '月度报告提交截止日期临近', '请完成本周数据统计工作', '设备巡检任务已分配给您']
}
const typeList = contents[type] || contents.system
return typeList[Math.floor(Math.random() * typeList.length)]
},
// 格式化时间
formatTime(date) {
const now = new Date()
const diff = now - date
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(diff / (1000 * 60 * 60))
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (minutes < 1) return '刚刚'
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
if (days < 7) return `${days}天前`
return `${date.getMonth() + 1}-${date.getDate()}`
},
// 更新标签计数
updateTabCounts() {
const tabs = this.data.tabs.map(tab => {
if (tab.key === 'all') {
tab.count = this.data.notificationList.filter(item => !item.isRead).length
} else {
tab.count = this.data.notificationList.filter(item => item.type === tab.key && !item.isRead).length
}
return tab
})
this.setData({ tabs })
},
// 标签切换
onTabChange(e) {
const { key } = e.currentTarget.dataset
this.setData({
selectedTab: key,
currentPage: 1,
notificationList: [],
hasMore: true
})
this.loadNotifications()
},
// 通知点击事件
onNotificationTap(e) {
const { item } = e.currentTarget.dataset
console.log('点击通知:', item)
// 标记为已读
if (!item.isRead) {
const notificationList = this.data.notificationList.map(notification => {
if (notification.id === item.id) {
notification.isRead = true
}
return notification
})
this.setData({ notificationList })
this.updateTabCounts()
}
// 根据通知类型跳转到相应页面
switch (item.type) {
case 'alert':
wx.navigateTo({
url: '/pages/alert/detail/detail?id=' + item.id
})
break
case 'task':
wx.navigateTo({
url: '/pages/task/detail/detail?id=' + item.id
})
break
default:
wx.showToast({
title: '查看详情功能开发中',
icon: 'none'
})
}
},
// 删除通知
onDeleteNotification(e) {
const { item } = e.currentTarget.dataset
wx.showModal({
title: '确认删除',
content: '确定要删除这条通知吗?',
success: (res) => {
if (res.confirm) {
const notificationList = this.data.notificationList.filter(notification => notification.id !== item.id)
this.setData({ notificationList })
this.updateTabCounts()
wx.showToast({
title: '删除成功',
icon: 'success'
})
}
}
})
},
// 全部已读
onMarkAllRead() {
const notificationList = this.data.notificationList.map(item => {
item.isRead = true
return item
})
this.setData({ notificationList })
this.updateTabCounts()
wx.showToast({
title: '已全部标记为已读',
icon: 'success'
})
},
// 加载更多
onLoadMore() {
this.loadNotifications()
}
})

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "消息通知",
"enablePullDownRefresh": true,
"backgroundColor": "#f5f5f5",
"backgroundTextStyle": "dark"
}

View File

@@ -0,0 +1,79 @@
<!-- 状态栏 -->
<view class="status-bar" style="height: {{statusBarHeight}}px;"></view>
<!-- 页面容器 -->
<view class="container">
<!-- 头部 -->
<view class="header">
<view class="header-title">消息通知</view>
<view class="header-actions">
<view class="action-btn" bindtap="onMarkAllRead">
<text class="action-text">全部已读</text>
</view>
</view>
</view>
<!-- 筛选标签 -->
<view class="filter-tabs">
<view
class="tab-item {{selectedTab === item.key ? 'active' : ''}}"
wx:for="{{tabs}}"
wx:key="key"
data-key="{{item.key}}"
bindtap="onTabChange"
>
<text class="tab-text">{{item.name}}</text>
<view class="tab-badge" wx:if="{{item.count > 0}}">{{item.count}}</view>
</view>
</view>
<!-- 通知列表 -->
<scroll-view class="notification-list" scroll-y="{{true}}" bindscrolltolower="onLoadMore">
<view
class="notification-item {{item.isRead ? 'read' : 'unread'}}"
wx:for="{{notificationList}}"
wx:key="id"
data-item="{{item}}"
bindtap="onNotificationTap"
>
<!-- 通知图标 -->
<view class="notification-icon">
<text class="icon-text">{{item.icon}}</text>
<view class="unread-dot" wx:if="{{!item.isRead}}"></view>
</view>
<!-- 通知内容 -->
<view class="notification-content">
<view class="notification-title">{{item.title}}</view>
<view class="notification-desc">{{item.content}}</view>
<view class="notification-meta">
<text class="notification-time">{{item.time}}</text>
<text class="notification-type">{{item.typeName}}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="notification-actions">
<view class="action-btn" data-item="{{item}}" bindtap="onDeleteNotification" catchtap="true">
<text class="action-icon">🗑️</text>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more" wx:if="{{hasMore}}">
<text class="load-text">{{loading ? '加载中...' : '上拉加载更多'}}</text>
</view>
<!-- 没有更多数据 -->
<view class="no-more" wx:if="{{!hasMore && notificationList.length > 0}}">
<text class="no-more-text">没有更多通知了</text>
</view>
<!-- 空状态 -->
<view class="empty-state" wx:if="{{notificationList.length === 0 && !loading}}">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无通知消息</text>
</view>
</scroll-view>
</view>

View File

@@ -0,0 +1,250 @@
/* 页面容器 */
.container {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 状态栏 */
.status-bar {
background-color: #4CAF50;
}
/* 头部 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
background-color: #4CAF50;
color: white;
}
.header-title {
font-size: 36rpx;
font-weight: bold;
}
.header-actions {
display: flex;
align-items: center;
}
.action-btn {
padding: 10rpx 20rpx;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
}
.action-text {
font-size: 28rpx;
color: white;
}
/* 筛选标签 */
.filter-tabs {
display: flex;
background-color: white;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
}
.tab-item {
position: relative;
display: flex;
align-items: center;
padding: 30rpx 20rpx;
margin-right: 40rpx;
border-bottom: 4rpx solid transparent;
transition: all 0.3s ease;
}
.tab-item.active {
border-bottom-color: #4CAF50;
}
.tab-text {
font-size: 30rpx;
color: #666;
}
.tab-item.active .tab-text {
color: #4CAF50;
font-weight: bold;
}
.tab-badge {
position: absolute;
top: 20rpx;
right: 10rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
font-size: 20rpx;
color: white;
background-color: #ff4757;
border-radius: 16rpx;
padding: 0 8rpx;
}
/* 通知列表 */
.notification-list {
height: calc(100vh - 200rpx);
padding: 20rpx 0;
}
.notification-item {
display: flex;
align-items: flex-start;
padding: 30rpx;
margin: 0 20rpx 20rpx;
background-color: white;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.notification-item.unread {
border-left: 6rpx solid #4CAF50;
}
.notification-item.read {
opacity: 0.7;
}
/* 通知图标 */
.notification-icon {
position: relative;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f0f9ff;
border-radius: 50%;
margin-right: 20rpx;
flex-shrink: 0;
}
.icon-text {
font-size: 40rpx;
}
.unread-dot {
position: absolute;
top: 10rpx;
right: 10rpx;
width: 16rpx;
height: 16rpx;
background-color: #ff4757;
border-radius: 50%;
}
/* 通知内容 */
.notification-content {
flex: 1;
min-width: 0;
}
.notification-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notification-desc {
font-size: 28rpx;
color: #666;
line-height: 1.5;
margin-bottom: 15rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.notification-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-time {
font-size: 24rpx;
color: #999;
}
.notification-type {
font-size: 24rpx;
color: #4CAF50;
background-color: rgba(76, 175, 80, 0.1);
padding: 4rpx 12rpx;
border-radius: 12rpx;
}
/* 操作按钮 */
.notification-actions {
display: flex;
flex-direction: column;
align-items: center;
margin-left: 20rpx;
}
.notification-actions .action-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border-radius: 50%;
}
.action-icon {
font-size: 28rpx;
}
/* 加载更多 */
.load-more {
text-align: center;
padding: 40rpx;
}
.load-text {
font-size: 28rpx;
color: #999;
}
/* 没有更多 */
.no-more {
text-align: center;
padding: 40rpx;
}
.no-more-text {
font-size: 28rpx;
color: #ccc;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 40rpx;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: #999;
}