222 lines
4.8 KiB
Vue
222 lines
4.8 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 消息通知页面
|
||
*
|
||
* 使用 Vant PullRefresh + List 实现下拉刷新和无限滚动,
|
||
* Tabs 切换"未读消息"和"全部消息"。
|
||
*/
|
||
import { ref, reactive } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { showToast } from 'vant'
|
||
|
||
const router = useRouter()
|
||
|
||
/** 当前激活的 Tab */
|
||
const activeName = ref('未读消息')
|
||
|
||
/** 列表数据 */
|
||
const list = ref<any[]>([])
|
||
|
||
/** 是否加载完成 */
|
||
const finished = ref(false)
|
||
|
||
/** 是否正在加载 */
|
||
const isLoading = ref(false)
|
||
|
||
/** 分页参数 */
|
||
const pageParams = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
})
|
||
|
||
/**
|
||
* 生成模拟消息数据
|
||
*/
|
||
function generateMockData(pageNum: number, pageSize: number) {
|
||
const start = (pageNum - 1) * pageSize
|
||
const total = 25
|
||
const items: any[] = []
|
||
const end = Math.min(start + pageSize, total)
|
||
for (let i = start; i < end; i++) {
|
||
items.push({
|
||
id: i + 1,
|
||
title: `通知消息标题 ${i + 1}`,
|
||
message: `这是第 ${i + 1} 条消息的详细内容,用于展示通知列表的效果。`,
|
||
createTime: `2025-06-${String(10 + Math.floor(i / 3)).padStart(2, '0')} 14:30:00`,
|
||
})
|
||
}
|
||
return { items, isLastPage: end >= total }
|
||
}
|
||
|
||
/**
|
||
* 获取列表数据
|
||
*/
|
||
function getList() {
|
||
isLoading.value = true
|
||
pageParams.pageNum = 1
|
||
setTimeout(() => {
|
||
const { items, isLastPage } = generateMockData(pageParams.pageNum, pageParams.pageSize)
|
||
list.value = items
|
||
finished.value = isLastPage
|
||
isLoading.value = false
|
||
}, 500)
|
||
}
|
||
|
||
/**
|
||
* Tab 切换
|
||
*/
|
||
function onChange(name: string | number) {
|
||
activeName.value = String(name)
|
||
finished.value = false
|
||
getList()
|
||
}
|
||
|
||
/**
|
||
* 无限滚动加载更多
|
||
*/
|
||
function onLoad() {
|
||
if (finished.value) return
|
||
isLoading.value = true
|
||
pageParams.pageNum += 1
|
||
setTimeout(() => {
|
||
const { items, isLastPage } = generateMockData(pageParams.pageNum, pageParams.pageSize)
|
||
list.value = list.value.concat(items)
|
||
finished.value = isLastPage
|
||
isLoading.value = false
|
||
}, 500)
|
||
}
|
||
|
||
/**
|
||
* 下拉刷新
|
||
*/
|
||
function onRefresh() {
|
||
finished.value = false
|
||
pageParams.pageNum = 1
|
||
setTimeout(() => {
|
||
const { items, isLastPage } = generateMockData(pageParams.pageNum, pageParams.pageSize)
|
||
list.value = items
|
||
finished.value = isLastPage
|
||
showToast('刷新成功')
|
||
}, 800)
|
||
}
|
||
|
||
/**
|
||
* 点击消息项
|
||
*/
|
||
function toDetail(item: any) {
|
||
showToast(`查看: ${item.title}`)
|
||
}
|
||
|
||
/** 初始加载 */
|
||
getList()
|
||
</script>
|
||
|
||
<template>
|
||
<div class="notice-page">
|
||
<!-- 顶部导航栏 -->
|
||
<van-nav-bar title="消息通知" left-text="返回" left-arrow fixed placeholder @click-left="router.back()" />
|
||
|
||
<!-- Tab 切换 -->
|
||
<van-tabs v-model:active="activeName" @change="onChange" sticky>
|
||
<van-tab title="未读消息" name="未读消息" />
|
||
<van-tab title="全部消息" name="全部消息" />
|
||
</van-tabs>
|
||
|
||
<!-- 下拉刷新 + 列表 -->
|
||
<van-pull-refresh v-model="isLoading" @refresh="onRefresh">
|
||
<van-list
|
||
v-model:loading="isLoading"
|
||
:finished="finished"
|
||
finished-text="没有更多了"
|
||
@load="onLoad"
|
||
>
|
||
<div
|
||
class="notice-item"
|
||
v-for="item in list"
|
||
:key="item.id"
|
||
@click="toDetail(item)"
|
||
>
|
||
<div class="notice-item-header">
|
||
<span class="notice-item-title">{{ item.title }}</span>
|
||
</div>
|
||
<van-divider />
|
||
<div class="notice-item-body">
|
||
<span>{{ item.message }}</span>
|
||
</div>
|
||
<van-divider />
|
||
<div class="notice-item-footer">
|
||
<span class="notice-item-time-label">通知时间:</span>
|
||
<span class="notice-item-time">{{ item.createTime }}</span>
|
||
</div>
|
||
</div>
|
||
</van-list>
|
||
</van-pull-refresh>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.notice-page {
|
||
min-height: 100vh;
|
||
background: #f4f7f8;
|
||
|
||
:deep(.van-nav-bar) {
|
||
background: var(--color-primary);
|
||
--van-nav-bar-title-text-color: #fff;
|
||
--van-nav-bar-text-color: #fff;
|
||
--van-nav-bar-icon-color: #fff;
|
||
}
|
||
}
|
||
|
||
.notice-item {
|
||
padding: 10px 12px;
|
||
margin: 12px 8px;
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
|
||
&:active {
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
&-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
&-title {
|
||
flex: 1;
|
||
font-size: 17px;
|
||
color: #333438;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&-body {
|
||
padding: 0 4px;
|
||
font-size: 14px;
|
||
color: #535c66;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
&-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
&-time-label {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
&-time {
|
||
font-size: 12px;
|
||
color: #666;
|
||
}
|
||
}
|
||
</style>
|