369 lines
8.5 KiB
Vue
369 lines
8.5 KiB
Vue
<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> |