Initial project commit
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onPullDownRefresh, onReachBottom, onShow } from "@dcloudio/uni-app";
|
||||
import { getNotifications, markAllRead, markRead } from "@/api/notification";
|
||||
import { extractList } from "@/utils/request";
|
||||
import { checkLogin } from "@/utils/auth";
|
||||
|
||||
onShow(() => { checkLogin(); });
|
||||
|
||||
const items = ref([]);
|
||||
const page = ref(1);
|
||||
const pageSize = 20;
|
||||
const hasMore = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const typeIcon = {
|
||||
audit: "checkbox-filled",
|
||||
comment: "chat",
|
||||
system: "info",
|
||||
};
|
||||
const typeColor = {
|
||||
audit: "#6366f1",
|
||||
comment: "#3b82f6",
|
||||
system: "#94a3b8",
|
||||
};
|
||||
|
||||
const fetchList = async (reset = false) => {
|
||||
if (loading.value) return;
|
||||
if (!reset && !hasMore.value) return;
|
||||
loading.value = true;
|
||||
if (reset) { page.value = 1; hasMore.value = true; }
|
||||
try {
|
||||
const res = await getNotifications({ page: page.value, page_size: pageSize });
|
||||
const list = extractList(res);
|
||||
if (reset) items.value = list; else items.value.push(...list);
|
||||
if (list.length < pageSize) hasMore.value = false; else page.value++;
|
||||
} catch (e) { console.error(e); }
|
||||
finally { loading.value = false; }
|
||||
};
|
||||
|
||||
const handleTap = async (item) => {
|
||||
if (!item.is_read) {
|
||||
try { await markRead(item.id); item.is_read = true; } catch (e) { /* */ }
|
||||
}
|
||||
if (item.ref_type === "spot" && item.ref_id) {
|
||||
uni.navigateTo({ url: `/pages/spot/detail?id=${item.ref_id}` });
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadAll = async () => {
|
||||
try {
|
||||
await markAllRead();
|
||||
items.value.forEach((n) => (n.is_read = true));
|
||||
uni.showToast({ title: "已全部标记已读", icon: "success" });
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
onPullDownRefresh(async () => { await fetchList(true); uni.stopPullDownRefresh(); });
|
||||
onReachBottom(() => { fetchList(); });
|
||||
|
||||
fetchList(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="notification-page">
|
||||
<view class="header-bar">
|
||||
<text class="header-title">消息通知</text>
|
||||
<text class="read-all" @tap="handleReadAll">全部已读</text>
|
||||
</view>
|
||||
|
||||
<view class="list">
|
||||
<view
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="noti-item"
|
||||
:class="{ unread: !item.is_read }"
|
||||
@tap="handleTap(item)"
|
||||
>
|
||||
<view class="noti-icon" :style="{ background: (typeColor[item.type] || '#94a3b8') + '18' }">
|
||||
<uni-icons :type="typeIcon[item.type] || 'info'" size="20" :color="typeColor[item.type] || '#94a3b8'" />
|
||||
</view>
|
||||
<view class="noti-body">
|
||||
<text class="noti-title">{{ item.title }}</text>
|
||||
<text v-if="item.content" class="noti-content">{{ item.content }}</text>
|
||||
<text class="noti-time">{{ item.created_at?.slice(0, 16).replace('T', ' ') }}</text>
|
||||
</view>
|
||||
<view v-if="!item.is_read" class="noti-dot" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="status-tip"><text>加载中...</text></view>
|
||||
<view v-else-if="!hasMore && items.length > 0" class="status-tip"><text>没有更多了</text></view>
|
||||
<view v-else-if="!loading && items.length === 0" class="empty-state">
|
||||
<uni-icons type="chat" size="48" color="#cbd5e1" />
|
||||
<text class="empty-text">暂无消息</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.notification-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
.header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
}
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
}
|
||||
.read-all {
|
||||
font-size: 26rpx;
|
||||
color: #6366f1;
|
||||
}
|
||||
.list {
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
.noti-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
position: relative;
|
||||
}
|
||||
.noti-item.unread {
|
||||
background: #f0f0ff;
|
||||
}
|
||||
.noti-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.noti-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.noti-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
display: block;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.noti-content {
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.noti-time {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.noti-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background: #ef4444;
|
||||
border-radius: 8rpx;
|
||||
position: absolute;
|
||||
top: 28rpx;
|
||||
right: 24rpx;
|
||||
}
|
||||
.status-tip {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
color: #94a3b8;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 160rpx 0;
|
||||
}
|
||||
.empty-text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user