appointment_system/pages/me/notification-page.vue
2025-12-15 23:42:31 +08:00

369 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="content">
<!-- 标题栏 -->
<view class="header-row">
<view class="back-button" @click="back">
<image src="/static/ic_back.png" class="back-icon"></image>
</view>
<text class="title">{{ $t('me.notification') }}</text>
<view class="mark-read-btn" @click="markAllRead">
<text class="mark-read-text">{{ $t('notification.markAllRead') }}</text>
</view>
</view>
<!-- Tab 区域 -->
<view class="tab-container">
<view class="tab-item" :class="{ active: activeTab === 'all' }" @click="switchTab('all')">
<text class="tab-text" :class="{ active: activeTab === 'all' }">{{ $t('notification.all') }}</text>
<view v-if="unreadCount.all > 0" class="badge">{{ unreadCount.all }}</view>
</view>
<view class="tab-item" :class="{ active: activeTab === 'system' }" @click="switchTab('system')">
<text class="tab-text"
:class="{ active: activeTab === 'system' }">{{ $t('notification.system') }}</text>
<view v-if="unreadCount.system > 0" class="badge">{{ unreadCount.system }}</view>
</view>
<view class="tab-item" :class="{ active: activeTab === 'activity' }" @click="switchTab('activity')">
<text class="tab-text"
:class="{ active: activeTab === 'activity' }">{{ $t('notification.activity') }}</text>
<view v-if="unreadCount.activity > 0" class="badge">{{ unreadCount.activity }}</view>
</view>
<view class="tab-item" :class="{ active: activeTab === 'service' }" @click="switchTab('service')">
<text class="tab-text"
:class="{ active: activeTab === 'service' }">{{ $t('notification.service') }}</text>
<view v-if="unreadCount.service > 0" class="badge">{{ unreadCount.service }}</view>
</view>
</view>
<!-- 通知列表 -->
<scroll-view class="notification-list" scroll-y @scrolltolower="loadMore" :show-scrollbar="false">
<view class="notification-item" :class="{ unread: !item.isRead }" v-for="item in filteredList"
:key="item.id" @click="readNotification(item)">
<view class="item-header">
<text class="item-title">{{ item.title }}</text>
<view v-if="!item.isRead" class="unread-dot"></view>
</view>
<text class="item-content">{{ item.content }}</text>
<text class="item-time">{{ formatTime(item.time) }}</text>
</view>
<!-- 加载更多 -->
<view v-if="loading" class="loading-more">
<text class="loading-text">{{ $t('common.loading') }}</text>
</view>
<!-- 空状态 -->
<view v-if="filteredList.length === 0 && !loading" class="empty-state">
<image src="/static/ic_notice.png" class="empty-icon" mode="aspectFit"></image>
<text class="no-data">{{ $t('notification.noNotification') }}</text>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
activeTab: 'all',
loading: false,
notificationList: [{
id: 1,
type: 'system',
title: '系统升级通知',
content: '我们将于今晚22:00-24:00进行系统维护升级届时部分功能可能无法使用请提前做好安排。',
time: Date.now() - 1000 * 60 * 5,
isRead: false
},
{
id: 2,
type: 'activity',
title: '限时优惠活动',
content: '新用户首单立减50元活动时间有限快来参与吧',
time: Date.now() - 1000 * 60 * 60 * 2,
isRead: false
},
{
id: 3,
type: 'service',
title: '预约成功提醒',
content: '您的机票预约已成功提交工作人员将在24小时内与您联系确认。',
time: Date.now() - 1000 * 60 * 60 * 24,
isRead: true
},
{
id: 4,
type: 'system',
title: '账户安全提醒',
content: '检测到您的账户在新设备登录,如非本人操作请及时修改密码。',
time: Date.now() - 1000 * 60 * 60 * 24 * 2,
isRead: true
},
{
id: 5,
type: 'service',
title: '服务完成通知',
content: '您预约的酒店服务已完成,感谢您的使用,期待再次为您服务!',
time: Date.now() - 1000 * 60 * 60 * 24 * 3,
isRead: true
}
]
}
},
computed: {
filteredList() {
if (this.activeTab === 'all') {
return this.notificationList;
}
return this.notificationList.filter(item => item.type === this.activeTab);
},
unreadCount() {
const counts = {
all: 0,
system: 0,
activity: 0,
service: 0
};
this.notificationList.forEach(item => {
if (!item.isRead) {
counts.all++;
counts[item.type]++;
}
});
return counts;
}
},
methods: {
back() {
uni.navigateBack({
delta: 1
});
},
switchTab(tab) {
this.activeTab = tab;
},
getTypeIcon(type) {
const icons = {
system: '/static/ic_notice.png',
activity: '/static/hot_server.png',
service: '/static/customer_service.png'
};
return icons[type] || '/static/ic_notice.png';
},
formatTime(timestamp) {
const now = Date.now();
const diff = now - timestamp;
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 this.$t('notification.justNow');
if (minutes < 60) return this.$t('notification.minutesAgo').replace('{n}', minutes);
if (hours < 24) return this.$t('notification.hoursAgo').replace('{n}', hours);
return this.$t('notification.daysAgo').replace('{n}', days);
},
readNotification(item) {
item.isRead = true;
// 可以在这里添加跳转到详情页的逻辑
},
markAllRead() {
this.notificationList.forEach(item => {
item.isRead = true;
});
uni.showToast({
title: this.$t('common.success'),
icon: 'success'
});
},
loadMore() {
// 加载更多逻辑
}
}
}
</script>
<style lang="scss">
.content {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #F3F3F3;
}
.header-row {
width: 100%;
margin-top: 100rpx;
padding-bottom: 20rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.back-button {
width: 80rpx;
height: 50rpx;
margin-left: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 48rpx;
height: 48rpx;
}
.title {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.mark-read-btn {
width: 80rpx;
margin-right: 32rpx;
display: flex;
justify-content: flex-end;
}
.mark-read-text {
font-size: 24rpx;
color: #00A0BC;
}
.tab-container {
display: flex;
flex-direction: row;
padding: 0 24rpx 24rpx;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 0;
position: relative;
margin: 0 8rpx;
border-radius: 50rpx;
border: 2rpx solid transparent;
&.active {
background-color: #E5FBFF;
border-color: #00A0BC;
}
}
.tab-text {
font-size: 26rpx;
color: #666;
&.active {
color: #00A0BC;
font-weight: 500;
}
}
.badge {
position: absolute;
top: 8rpx;
right: 4rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
background-color: #FF3B30;
border-radius: 16rpx;
font-size: 20rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.notification-list {
flex: 1;
height: 0;
}
.notification-item {
width: 686rpx;
background-color: #fff;
border-radius: 20rpx;
padding: 36rpx 32rpx;
margin: 0 auto 24rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.08);
box-sizing: border-box;
&.unread {
background-color: #fff;
}
}
.item-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 16rpx;
}
.item-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
flex: 1;
}
.unread-dot {
width: 12rpx;
height: 12rpx;
background-color: #FF3B30;
border-radius: 50%;
margin-left: 16rpx;
flex-shrink: 0;
}
.item-content {
font-size: 28rpx;
color: #666;
line-height: 1.7;
display: block;
margin-bottom: 16rpx;
}
.item-time {
font-size: 24rpx;
color: #999;
display: block;
}
.loading-more {
padding: 40rpx;
display: flex;
justify-content: center;
}
.loading-text {
font-size: 26rpx;
color: #999;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 200rpx;
}
.empty-icon {
width: 200rpx;
height: 200rpx;
opacity: 0.3;
margin-bottom: 40rpx;
}
.no-data {
font-size: 28rpx;
color: #999;
}
</style>