hookehuyr

feat: 新增消息中心功能模块

添加消息列表页和消息详情页,支持查看系统消息详情
在个人中心菜单中增加消息入口,并更新组件类型声明
...@@ -16,8 +16,8 @@ declare module 'vue' { ...@@ -16,8 +16,8 @@ declare module 'vue' {
16 NavHeader: typeof import('./src/components/NavHeader.vue')['default'] 16 NavHeader: typeof import('./src/components/NavHeader.vue')['default']
17 NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] 17 NutAvatar: typeof import('@nutui/nutui-taro')['Avatar']
18 NutButton: typeof import('@nutui/nutui-taro')['Button'] 18 NutButton: typeof import('@nutui/nutui-taro')['Button']
19 + NutEmpty: typeof import('@nutui/nutui-taro')['Empty']
19 NutInput: typeof import('@nutui/nutui-taro')['Input'] 20 NutInput: typeof import('@nutui/nutui-taro')['Input']
20 - NutLoading: typeof import('@nutui/nutui-taro')['Loading']
21 NutPicker: typeof import('@nutui/nutui-taro')['Picker'] 21 NutPicker: typeof import('@nutui/nutui-taro')['Picker']
22 NutPopup: typeof import('@nutui/nutui-taro')['Popup'] 22 NutPopup: typeof import('@nutui/nutui-taro')['Popup']
23 NutRadio: typeof import('@nutui/nutui-taro')['Radio'] 23 NutRadio: typeof import('@nutui/nutui-taro')['Radio']
......
...@@ -26,6 +26,8 @@ const pages = [ ...@@ -26,6 +26,8 @@ const pages = [
26 'pages/feedback/index', 26 'pages/feedback/index',
27 'pages/login/index', 27 'pages/login/index',
28 'pages/help-center/index', 28 'pages/help-center/index',
29 + 'pages/message/index',
30 + 'pages/message-detail/index',
29 ] 31 ]
30 32
31 if (process.env.NODE_ENV === 'development') { 33 if (process.env.NODE_ENV === 'development') {
......
1 +export default {
2 + navigationBarTitleText: '消息详情',
3 + backgroundColor: '#ffffff',
4 + navigationStyle: 'custom'
5 +}
1 +<!--
2 + * @Date: 2026-02-03 21:26:58
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2026-02-03 21:30:21
5 + * @FilePath: /manulife-weapp/src/pages/message-detail/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="min-h-screen bg-white pb-safe">
10 + <NavHeader title="消息详情" />
11 +
12 + <view v-if="detail" class="p-5">
13 + <!-- 标题 -->
14 + <view class="text-xl font-bold text-gray-900 mb-3 leading-tight">
15 + {{ detail.title }}
16 + </view>
17 +
18 + <!-- 元信息 -->
19 + <view class="flex items-center text-xs text-gray-400 mb-6">
20 + <text>{{ detail.create_time }}</text>
21 + <text class="mx-2">·</text>
22 + <text>Manulife</text>
23 + </view>
24 +
25 + <!-- 内容区域 -->
26 + <view class="rich-text-content">
27 + <rich-text :nodes="formattedContent" />
28 + </view>
29 + </view>
30 +
31 + <!-- 加载中 -->
32 + <view v-else-if="loading" class="flex justify-center py-10">
33 + <view class="w-8 h-8 border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin"></view>
34 + </view>
35 +
36 + <!-- 错误/空状态 -->
37 + <nut-empty v-else description="未找到消息内容" image="error" />
38 + </view>
39 +</template>
40 +
41 +<script setup>
42 +import { ref, computed } from 'vue'
43 +import { useLoad } from '@tarojs/taro'
44 +import NavHeader from '@/components/NavHeader.vue'
45 +import { detailAPI } from '@/api/news'
46 +
47 +const detail = ref(null)
48 +const loading = ref(true)
49 +
50 +/**
51 + * @description 格式化富文本内容,处理图片宽度等问题
52 + */
53 +const formattedContent = computed(() => {
54 + if (!detail.value?.content) return ''
55 + // 简单的正则替换,确保图片宽度不超过容器
56 + return detail.value.content.replace(
57 + /<img/g,
58 + '<img style="max-width:100%;height:auto;display:block;"'
59 + )
60 +})
61 +
62 +/**
63 + * @description 获取消息详情
64 + * @param {string|number} id 消息ID
65 + */
66 +const fetchDetail = async (id) => {
67 + loading.value = true
68 + try {
69 + const res = await detailAPI({ i: id })
70 + if (res.code === 1) {
71 + detail.value = res.data
72 + }
73 + } catch (err) {
74 + console.error('获取消息详情失败:', err)
75 + } finally {
76 + loading.value = false
77 + }
78 +}
79 +
80 +useLoad((options) => {
81 + if (options.id) {
82 + fetchDetail(options.id)
83 + }
84 +})
85 +</script>
86 +
87 +<style lang="less">
88 +.rich-text-content {
89 + font-size: 28rpx;
90 + color: #333;
91 + line-height: 1.8;
92 +
93 + /* 确保富文本样式正确 */
94 + p {
95 + margin-bottom: 20rpx;
96 + }
97 +}
98 +</style>
1 +/*
2 + * @Date: 2026-02-03 21:26:43
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2026-02-03 21:33:27
5 + * @FilePath: /manulife-weapp/src/pages/message/index.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + navigationBarTitleText: '我的消息',
10 + enablePullDownRefresh: true,
11 + backgroundColor: '#f9fafb',
12 + navigationStyle: 'custom'
13 +}
1 +<template>
2 + <view class="min-h-screen bg-[#F9FAFB] pb-safe">
3 + <NavHeader title="我的消息" />
4 +
5 + <!-- 列表区域 -->
6 + <view class="p-4">
7 + <template v-if="messageList.length > 0">
8 + <view
9 + v-for="item in messageList"
10 + :key="item.id"
11 + class="bg-white rounded-xl p-4 mb-3 shadow-sm active:opacity-70 transition-opacity"
12 + @tap="handleItemClick(item)"
13 + >
14 + <view class="flex justify-between items-start mb-2">
15 + <view class="flex-1 mr-2">
16 + <view class="text-base font-bold text-gray-900 line-clamp-1">
17 + {{ item.title }}
18 + </view>
19 + </view>
20 + <text class="text-xs text-gray-400 shrink-0 mt-1">
21 + {{ item.create_time }}
22 + </text>
23 + </view>
24 +
25 + <view class="text-sm text-gray-600 line-clamp-2 leading-relaxed">
26 + {{ item.intro || item.content || '暂无简介' }}
27 + </view>
28 + </view>
29 +
30 + <!-- 加载更多/没有更多 -->
31 + <view class="py-4 text-center text-xs text-gray-400">
32 + <text v-if="loading">加载中...</text>
33 + <text v-else-if="!hasMore">没有更多了</text>
34 + <text v-else>上拉加载更多</text>
35 + </view>
36 + </template>
37 +
38 + <!-- 空状态 -->
39 + <nut-empty
40 + v-else-if="!loading && messageList.length === 0"
41 + description="暂无消息"
42 + image="empty"
43 + />
44 + </view>
45 + </view>
46 +</template>
47 +
48 +<script setup>
49 +import { ref } from 'vue'
50 +import { useLoad, usePullDownRefresh, useReachBottom, stopPullDownRefresh } from '@tarojs/taro'
51 +import { useGo } from '@/hooks/useGo'
52 +import NavHeader from '@/components/NavHeader.vue'
53 +import { myListAPI } from '@/api/news'
54 +
55 +const go = useGo()
56 +
57 +const messageList = ref([])
58 +const page = ref(1)
59 +const limit = ref(10)
60 +const hasMore = ref(true)
61 +const loading = ref(false)
62 +
63 +/**
64 + * @description 加载消息列表
65 + * @param {boolean} refresh 是否刷新
66 + */
67 +const fetchMessageList = async (refresh = false) => {
68 + if (loading.value) return
69 +
70 + if (refresh) {
71 + page.value = 1
72 + hasMore.value = true
73 + } else if (!hasMore.value) {
74 + return
75 + }
76 +
77 + loading.value = true
78 +
79 + try {
80 + const res = await myListAPI({
81 + page: page.value,
82 + limit: limit.value
83 + })
84 +
85 + if (res.code === 1) {
86 + const list = res.data?.list || []
87 +
88 + if (refresh) {
89 + messageList.value = list
90 + } else {
91 + messageList.value = [...messageList.value, ...list]
92 + }
93 +
94 + if (list.length < limit.value) {
95 + hasMore.value = false
96 + } else {
97 + page.value++
98 + }
99 + }
100 + } catch (err) {
101 + console.error('获取消息列表失败:', err)
102 + } finally {
103 + loading.value = false
104 + if (refresh) {
105 + stopPullDownRefresh()
106 + }
107 + }
108 +}
109 +
110 +/**
111 + * @description 跳转到详情页
112 + * @param {Object} item 消息对象
113 + */
114 +const handleItemClick = (item) => {
115 + go('/pages/message-detail/index', { id: item.id })
116 +}
117 +
118 +// 页面加载
119 +useLoad(() => {
120 + fetchMessageList(true)
121 +})
122 +
123 +// 下拉刷新
124 +usePullDownRefresh(() => {
125 + fetchMessageList(true)
126 +})
127 +
128 +// 上拉加载更多
129 +useReachBottom(() => {
130 + fetchMessageList()
131 +})
132 +</script>
133 +
134 +<style lang="less">
135 +/* Scoped styles if needed */
136 +</style>
...@@ -128,6 +128,7 @@ useDidShow(() => { ...@@ -128,6 +128,7 @@ useDidShow(() => {
128 128
129 const menuItems = [ 129 const menuItems = [
130 { title: '我的计划书', icon: 'order', path: '/pages/plan/index' }, 130 { title: '我的计划书', icon: 'order', path: '/pages/plan/index' },
131 + { title: '我的消息', icon: 'message', path: '/pages/message/index' },
131 { title: '我的收藏', icon: 'star', path: '/pages/favorites/index' }, 132 { title: '我的收藏', icon: 'star', path: '/pages/favorites/index' },
132 { title: '帮助中心', icon: 'service', path: '/pages/help-center/index' }, 133 { title: '帮助中心', icon: 'service', path: '/pages/help-center/index' },
133 { title: '意见反馈', icon: 'edit', path: '/pages/feedback-list/index' } 134 { title: '意见反馈', icon: 'edit', path: '/pages/feedback-list/index' }
......