This commit is contained in:
@@ -17,7 +17,8 @@
|
||||
<WifiCard v-if="userInfo.isDevice" :deviceInfo="deviceInfo" @modify="modifyWifi" @copy="copy" />
|
||||
|
||||
<FunctionCard :realNameStatus="realNameStatus" :alreadyBindPhone="alreadyBindPhone"
|
||||
:isDevice="userInfo.isDevice" :walletBalance="deviceInfo.walletBalance" @enter="enterDetail" @sync="onSync">
|
||||
:isDevice="userInfo.isDevice" :walletBalance="deviceInfo.walletBalance" :unreadCount="notificationUnreadCount"
|
||||
@enter="enterDetail" @sync="onSync">
|
||||
<!-- 修改WIFI弹窗 -->
|
||||
<up-popup :show="showModifyWifi" mode="center" @close="showModifyWifi=false">
|
||||
<view class="wifi-popup">
|
||||
@@ -124,7 +125,8 @@
|
||||
import FloatingButton from '@/components/FloatingButton.vue';
|
||||
import {
|
||||
assetApi,
|
||||
deviceApi
|
||||
deviceApi,
|
||||
notificationApi
|
||||
} from '@/api/index.js';
|
||||
import {
|
||||
useUserStore
|
||||
@@ -210,6 +212,7 @@
|
||||
let smsCodePhone = ref('');
|
||||
let smsCode = ref('');
|
||||
let indexEntryChecking = ref(false);
|
||||
let notificationUnreadCount = ref(0);
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '-';
|
||||
@@ -699,6 +702,11 @@
|
||||
|
||||
const enterDetail = (name) => {
|
||||
switch (name) {
|
||||
case 'notifications':
|
||||
uni.navigateTo({
|
||||
url: '/pages/notifications/notifications'
|
||||
});
|
||||
break;
|
||||
case 'package-order':
|
||||
uni.navigateTo({
|
||||
url: '/pages/package-order/package-order'
|
||||
@@ -767,12 +775,22 @@
|
||||
}
|
||||
};
|
||||
|
||||
const loadNotificationUnreadCount = async () => {
|
||||
try {
|
||||
const data = await notificationApi.getUnreadCount();
|
||||
notificationUnreadCount.value = Number(data?.count || 0);
|
||||
} catch (error) {
|
||||
console.error('加载通知未读数失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initCurrentMonth();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
handleIndexEntry();
|
||||
loadNotificationUnreadCount();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
160
pages/notifications/notifications.vue
Normal file
160
pages/notifications/notifications.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="toolbar flex-row-sb">
|
||||
<view class="summary">共 {{ total }} 条通知</view>
|
||||
<button class="btn-apple btn-secondary mark-all" :disabled="unreadCount === 0" @tap="markAllRead">
|
||||
全部已读
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="notifications.length === 0 && !loading" class="empty-state">
|
||||
<image src="/static/tips.png" mode="aspectFit" class="empty-icon" />
|
||||
<view class="empty-title">暂无通知</view>
|
||||
<view class="empty-desc">当前没有可查看的业务通知</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="notification-list">
|
||||
<view v-for="item in notifications" :key="item.id" class="card notification-card"
|
||||
:class="{ unread: !item.is_read }" @tap="markItemRead(item)">
|
||||
<view class="notification-header flex-row-sb">
|
||||
<view class="notification-title">{{ item.title || '业务通知' }}</view>
|
||||
<view v-if="!item.is_read" class="unread-dot"></view>
|
||||
</view>
|
||||
<view class="notification-body">{{ item.body || '-' }}</view>
|
||||
<view class="notification-footer flex-row-sb">
|
||||
<view class="notification-meta">{{ getCategoryText(item.category) }} · {{ getSeverityText(item.severity) }}</view>
|
||||
<view class="notification-time">{{ formatDate(item.created_at) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="notifications.length > 0" class="load-more" @tap="loadMore">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
<text v-else>点击加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { notificationApi } from '@/api/index.js';
|
||||
|
||||
const pageSize = 20;
|
||||
const notifications = reactive([]);
|
||||
const page = ref(1);
|
||||
const total = ref(0);
|
||||
const unreadCount = ref(0);
|
||||
const loading = ref(false);
|
||||
const noMore = ref(false);
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!value) return '-';
|
||||
return value.replace('T', ' ').slice(0, 16);
|
||||
};
|
||||
|
||||
const getCategoryText = (category) => ({
|
||||
approval: '审批',
|
||||
expiry: '临期',
|
||||
sync: '同步',
|
||||
system: '系统'
|
||||
}[category] || '通知');
|
||||
|
||||
const getSeverityText = (severity) => ({
|
||||
info: '提示',
|
||||
warning: '警告',
|
||||
error: '错误',
|
||||
critical: '严重'
|
||||
}[severity] || '提示');
|
||||
|
||||
const loadUnreadCount = async () => {
|
||||
try {
|
||||
const data = await notificationApi.getUnreadCount();
|
||||
unreadCount.value = Number(data?.count || 0);
|
||||
} catch (error) {
|
||||
console.error('加载通知未读数失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadNotifications = async (append = false) => {
|
||||
if (loading.value || (append && noMore.value)) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await notificationApi.getList(page.value, pageSize);
|
||||
const items = data?.items || [];
|
||||
if (append) {
|
||||
notifications.push(...items);
|
||||
} else {
|
||||
notifications.splice(0, notifications.length, ...items);
|
||||
}
|
||||
total.value = Number(data?.total || 0);
|
||||
noMore.value = notifications.length >= total.value || items.length < pageSize;
|
||||
if (!noMore.value) page.value += 1;
|
||||
} catch (error) {
|
||||
console.error('加载通知列表失败', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const markItemRead = async (item) => {
|
||||
if (item.is_read) return;
|
||||
try {
|
||||
await notificationApi.markRead(item.id);
|
||||
item.is_read = true;
|
||||
item.read_at = new Date().toISOString();
|
||||
unreadCount.value = Math.max(unreadCount.value - 1, 0);
|
||||
} catch (error) {
|
||||
console.error('标记通知已读失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const markAllRead = async () => {
|
||||
if (!unreadCount.value) return;
|
||||
try {
|
||||
await notificationApi.markAllRead();
|
||||
notifications.forEach(item => {
|
||||
item.is_read = true;
|
||||
item.read_at = item.read_at || new Date().toISOString();
|
||||
});
|
||||
unreadCount.value = 0;
|
||||
uni.showToast({ title: '已全部标记为已读', icon: 'success' });
|
||||
} catch (error) {
|
||||
console.error('全部标记通知已读失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
if (!noMore.value) loadNotifications(true);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadNotifications();
|
||||
loadUnreadCount();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
loadUnreadCount();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container { padding-bottom: 40rpx; }
|
||||
.toolbar { margin-bottom: var(--space-md); }
|
||||
.summary { color: var(--text-tertiary); font-size: 24rpx; }
|
||||
.mark-all { margin: 0; padding: 0 24rpx; height: 64rpx; line-height: 64rpx; font-size: 24rpx; }
|
||||
.notification-card { margin-bottom: var(--space-md); border-left: 6rpx solid transparent; }
|
||||
.notification-card.unread { border-left-color: var(--primary); }
|
||||
.notification-header { margin-bottom: var(--space-sm); }
|
||||
.notification-title { color: var(--text-primary); font-size: 30rpx; font-weight: 600; flex: 1; }
|
||||
.unread-dot { width: 14rpx; height: 14rpx; margin-left: 16rpx; border-radius: 50%; background: var(--primary); }
|
||||
.notification-body { color: var(--text-secondary); font-size: 26rpx; line-height: 1.6; white-space: pre-wrap; }
|
||||
.notification-footer { margin-top: var(--space-md); }
|
||||
.notification-meta, .notification-time { color: var(--text-tertiary); font-size: 22rpx; }
|
||||
.empty-state { display: flex; flex-direction: column; align-items: center; padding: 140rpx 40rpx; }
|
||||
.empty-icon { width: 100rpx; height: 100rpx; opacity: 0.6; margin-bottom: 24rpx; }
|
||||
.empty-title { color: var(--text-primary); font-size: 30rpx; font-weight: 600; }
|
||||
.empty-desc { color: var(--text-tertiary); font-size: 24rpx; margin-top: 12rpx; }
|
||||
.load-more { padding: var(--space-lg); color: var(--text-tertiary); text-align: center; font-size: 24rpx; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user