hookehuyr

feat(user): 更新消息列表和详情页以匹配API规范

...@@ -5,6 +5,18 @@ ...@@ -5,6 +5,18 @@
5 5
6 --- 6 ---
7 7
8 +## [2026-02-12] - 优化消息列表卡片布局
9 +
10 +### 优化
11 +- 重构消息列表卡片布局,提升信息可读性
12 +- 简化卡片结构:第一行展示消息内容,第二行左侧展示时间,右侧展示状态
13 +- 调整标题、时间、状态的视觉层级
14 +- 使用 `IconFont` 组件替换旧的图标实现
15 +- 优化消息详情页布局:移除标题提取逻辑,仅展示“发送时间”(右上角)和完整内容,避免内容重复显示
16 +- 增加未读消息红点提示
17 +
18 +---
19 +
8 ## [2026-02-12] - 修复结果页返回后表单未重置 20 ## [2026-02-12] - 修复结果页返回后表单未重置
9 21
10 ### 修复 22 ### 修复
......
...@@ -8,7 +8,7 @@ info: ...@@ -8,7 +8,7 @@ info:
8 title: '' 8 title: ''
9 version: 1.0.0 9 version: 1.0.0
10 paths: 10 paths:
11 - /: 11 + /srv/:
12 get: 12 get:
13 summary: 消息详情 13 summary: 消息详情
14 deprecated: false 14 deprecated: false
...@@ -41,7 +41,7 @@ paths: ...@@ -41,7 +41,7 @@ paths:
41 in: query 41 in: query
42 description: 消息ID 42 description: 消息ID
43 required: true 43 required: true
44 - example: '817677' 44 + example: '834791'
45 schema: 45 schema:
46 type: string 46 type: string
47 responses: 47 responses:
...@@ -57,32 +57,35 @@ paths: ...@@ -57,32 +57,35 @@ paths:
57 msg: 57 msg:
58 type: string 58 type: string
59 data: 59 data:
60 - type: array 60 + type: object
61 - items: 61 + properties:
62 - type: object 62 + id:
63 - properties: 63 + type: integer
64 - id: 64 + title: 消息id
65 - type: integer 65 + note:
66 - title: 消息id 66 + type: string
67 - note: 67 + title: 消息内容
68 - type: string 68 + created_time:
69 - title: 消息内容 69 + type: string
70 - created_time: 70 + title: 发消息的时间
71 - type: string 71 + status:
72 - title: 发消息的时间 72 + type: string
73 - status: 73 + title: 状态
74 - type: string 74 + description: send=以发送未读取,read=已读取
75 - title: 状态 75 + pk_id:
76 - description: send=以发送未读取,read=已读取 76 + type: integer
77 - x-apifox-orders: 77 + title: 计划书订单ID
78 - - id 78 + x-apifox-orders:
79 - - note 79 + - id
80 - - created_time 80 + - note
81 - - status 81 + - created_time
82 - required: 82 + - status
83 - - note 83 + - pk_id
84 - - created_time 84 + required:
85 - - status 85 + - note
86 + - created_time
87 + - status
88 + - pk_id
86 required: 89 required:
87 - code 90 - code
88 - msg 91 - msg
...@@ -96,7 +99,7 @@ paths: ...@@ -96,7 +99,7 @@ paths:
96 x-apifox-ordering: 0 99 x-apifox-ordering: 0
97 security: [] 100 security: []
98 x-apifox-folder: 消息 101 x-apifox-folder: 消息
99 - x-apifox-status: developing 102 + x-apifox-status: testing
100 x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-413906673-run 103 x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-413906673-run
101 components: 104 components:
102 schemas: {} 105 schemas: {}
......
...@@ -64,32 +64,48 @@ paths: ...@@ -64,32 +64,48 @@ paths:
64 msg: 64 msg:
65 type: string 65 type: string
66 data: 66 data:
67 - type: array 67 + type: object
68 - items: 68 + properties:
69 - type: object 69 + list:
70 - properties: 70 + type: array
71 - id: 71 + items:
72 - type: integer 72 + type: object
73 - title: 消息id 73 + properties:
74 - note: 74 + id:
75 - type: string 75 + type: integer
76 - title: 消息内容 76 + title: 消息id
77 - created_time: 77 + note:
78 - type: string 78 + type: string
79 - title: 发消息的时间 79 + title: 消息内容
80 - status: 80 + created_time:
81 - type: string 81 + type: string
82 - title: 状态 82 + title: 发消息的时间
83 - description: send=以发送未读取,read=已读取 83 + status:
84 - x-apifox-orders: 84 + type: string
85 - - id 85 + title: 状态
86 - - note 86 + description: send=以发送未读取,read=已读取
87 - - created_time 87 + pk_id:
88 - - status 88 + type: integer
89 - required: 89 + title: 计划书订单ID
90 - - note 90 + x-apifox-orders:
91 - - created_time 91 + - id
92 - - status 92 + - note
93 + - created_time
94 + - status
95 + - pk_id
96 + required:
97 + - note
98 + - created_time
99 + - status
100 + - pk_id
101 + total:
102 + type: integer
103 + x-apifox-orders:
104 + - list
105 + - total
106 + required:
107 + - list
108 + - total
93 required: 109 required:
94 - code 110 - code
95 - msg 111 - msg
...@@ -103,7 +119,7 @@ paths: ...@@ -103,7 +119,7 @@ paths:
103 x-apifox-ordering: 0 119 x-apifox-ordering: 0
104 security: [] 120 security: []
105 x-apifox-folder: 消息 121 x-apifox-folder: 消息
106 - x-apifox-status: developing 122 + x-apifox-status: testing
107 x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-413906672-run 123 x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-413906672-run
108 components: 124 components:
109 schemas: {} 125 schemas: {}
......
...@@ -13,12 +13,13 @@ const Api = { ...@@ -13,12 +13,13 @@ const Api = {
13 * @returns {Promise<{ 13 * @returns {Promise<{
14 * code: number; // 状态码 14 * code: number; // 状态码
15 * msg: string; // 消息 15 * msg: string; // 消息
16 - * data: Array<{ 16 + * data: {
17 id: integer; // 消息id 17 id: integer; // 消息id
18 note: string; // 消息内容 18 note: string; // 消息内容
19 created_time: string; // 发消息的时间 19 created_time: string; // 发消息的时间
20 status: string; // send=以发送未读取,read=已读取 20 status: string; // send=以发送未读取,read=已读取
21 - * }>; 21 + pk_id: integer; // 计划书订单ID
22 + * };
22 * }>} 23 * }>}
23 */ 24 */
24 export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); 25 export const detailAPI = (params) => fn(fetch.get(Api.Detail, params));
...@@ -32,12 +33,16 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); ...@@ -32,12 +33,16 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params));
32 * @returns {Promise<{ 33 * @returns {Promise<{
33 * code: number; // 状态码 34 * code: number; // 状态码
34 * msg: string; // 消息 35 * msg: string; // 消息
35 - * data: Array<{ 36 + * data: {
36 - id: integer; // 消息id 37 + list: Array<{
37 - note: string; // 消息内容 38 + id: integer; // 消息id
38 - created_time: string; // 发消息的时间 39 + note: string; // 消息内容
39 - status: string; // send=以发送未读取,read=已读取 40 + created_time: string; // 发消息的时间
40 - * }>; 41 + status: string; // send=以发送未读取,read=已读取
42 + pk_id: integer; // 计划书订单ID
43 + }>;
44 + total: integer; //
45 + * };
41 * }>} 46 * }>}
42 */ 47 */
43 export const myListAPI = (params) => fn(fetch.get(Api.MyList, params)); 48 export const myListAPI = (params) => fn(fetch.get(Api.MyList, params));
......
1 <!-- 1 <!--
2 - * @Date: 2026-02-03 21:26:58 2 + * @Date:2026-02-03 21:26:58
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2026-02-03 21:30:21 4 + * @LastEditTime: 2026-02-12 18:24:35
5 * @FilePath: /manulife-weapp/src/pages/message-detail/index.vue 5 * @FilePath: /manulife-weapp/src/pages/message-detail/index.vue
6 - * @Description: 文件描述 6 + * @Description: 消息详情页
7 --> 7 -->
8 <template> 8 <template>
9 <view class="min-h-screen bg-white pb-safe"> 9 <view class="min-h-screen bg-white pb-safe">
10 <NavHeader title="消息详情" /> 10 <NavHeader title="消息详情" />
11 11
12 <view v-if="detail" class="p-5"> 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 13
25 <!-- 内容区域 --> 14 <!-- 内容区域 -->
26 <view class="rich-text-content"> 15 <view class="rich-text-content">
27 <rich-text :nodes="formattedContent" /> 16 <rich-text :nodes="formattedContent" />
28 </view> 17 </view>
18 +
19 + <!-- 顶部时间 -->
20 + <view class="mt-4 text-right">
21 + <text class="text-xs text-gray-400">{{ detail.created_time }}</text>
22 + </view>
23 +
24 + <!-- 关联计划书(可选) -->
25 + <!-- <view v-if="detail.pk_id" class="mt-2 pt-4 border-t border-gray-200">
26 + <view class="text-sm text-gray-500 mb-2">关联计划书</view>
27 + <view class="text-xs text-gray-400">订单ID: {{ detail.pk_id }}</view>
28 + </view> -->
29 </view> 29 </view>
30 30
31 <!-- 加载中 --> 31 <!-- 加载中 -->
...@@ -51,12 +51,15 @@ const loading = ref(true) ...@@ -51,12 +51,15 @@ const loading = ref(true)
51 * @description 格式化富文本内容,处理图片宽度等问题 51 * @description 格式化富文本内容,处理图片宽度等问题
52 */ 52 */
53 const formattedContent = computed(() => { 53 const formattedContent = computed(() => {
54 - if (!detail.value?.content) return '' 54 + if (!detail.value?.note) return ''
55 +
55 // 简单的正则替换,确保图片宽度不超过容器 56 // 简单的正则替换,确保图片宽度不超过容器
56 - return detail.value.content.replace( 57 + const content = detail.value.note.replace(
57 /<img/g, 58 /<img/g,
58 '<img style="max-width:100%;height:auto;display:block;"' 59 '<img style="max-width:100%;height:auto;display:block;"'
59 ) 60 )
61 +
62 + return content
60 }) 63 })
61 64
62 /** 65 /**
......
1 <!-- 1 <!--
2 - * @Date: 2026-02-08 2 + * @Date:2026-02-08
3 * @Description: 我的消息页 - 使用 LoadMoreList 组件重构版本 3 * @Description: 我的消息页 - 使用 LoadMoreList 组件重构版本
4 --> 4 -->
5 <template> 5 <template>
...@@ -27,19 +27,26 @@ ...@@ -27,19 +27,26 @@
27 class="message-item bg-white rounded-xl p-4 mb-3 shadow-sm active:opacity-70 transition-opacity" 27 class="message-item bg-white rounded-xl p-4 mb-3 shadow-sm active:opacity-70 transition-opacity"
28 @tap="handleItemClick(item)" 28 @tap="handleItemClick(item)"
29 > 29 >
30 - <view class="flex justify-between items-start mb-2"> 30 + <!-- 第一行:内容(带红点) -->
31 - <view class="flex-1 mr-2"> 31 + <view class="text-base font-bold text-gray-900 line-clamp-1 mb-3 flex items-center">
32 - <view class="text-base font-bold text-gray-900 line-clamp-1"> 32 + <view v-if="item.status === 'send'" class="w-2 h-2 bg-red-500 rounded-full mr-2 shrink-0"></view>
33 - {{ item.title }} 33 + {{ getItemTitle(item.note) }}
34 - </view>
35 - </view>
36 - <text class="text-xs text-gray-400 shrink-0 mt-1">
37 - {{ item.create_time }}
38 - </text>
39 </view> 34 </view>
40 35
41 - <view class="text-sm text-gray-600 line-clamp-2 leading-relaxed"> 36 + <!-- 第二行:时间(左)与 状态(右) -->
42 - {{ item.intro || item.content || '暂无简介' }} 37 + <view class="flex justify-between items-center">
38 + <!-- 左边:时间 -->
39 + <text class="text-xs text-gray-400 font-medium">{{ item.created_time }}</text>
40 +
41 + <!-- 右边:状态 -->
42 + <view class="flex items-center">
43 + <view v-if="item.status === 'send'" class="px-2 py-0.5 bg-red-50 text-red-500 rounded text-xs font-medium">
44 + 未读
45 + </view>
46 + <view v-else-if="item.status === 'read'" class="px-2 py-0.5 bg-gray-100 text-gray-400 rounded text-xs">
47 + 已读
48 + </view>
49 + </view>
43 </view> 50 </view>
44 </view> 51 </view>
45 </template> 52 </template>
...@@ -58,6 +65,7 @@ import { useLoad } from '@tarojs/taro' ...@@ -58,6 +65,7 @@ import { useLoad } from '@tarojs/taro'
58 import { useGo } from '@/hooks/useGo' 65 import { useGo } from '@/hooks/useGo'
59 import LoadMoreList from '@/components/list/LoadMoreList' 66 import LoadMoreList from '@/components/list/LoadMoreList'
60 import NavHeader from '@/components/navigation/NavHeader.vue' 67 import NavHeader from '@/components/navigation/NavHeader.vue'
68 +import IconFont from '@/components/icons/IconFont.vue'
61 import { myListAPI } from '@/api/news' 69 import { myListAPI } from '@/api/news'
62 import { mockMessageListAPI } from '@/utils/mockData' 70 import { mockMessageListAPI } from '@/utils/mockData'
63 71
...@@ -67,41 +75,52 @@ const USE_MOCK_DATA = false ...@@ -67,41 +75,52 @@ const USE_MOCK_DATA = false
67 75
68 const go = useGo() 76 const go = useGo()
69 77
70 -/** 78 +// 响应式状态
71 - * 当前列表数据
72 - * @type {Ref<Array<any>>}
73 - */
74 const currentList = ref([]) 79 const currentList = ref([])
75 -
76 -/**
77 - * 当前页码(从0开始)
78 - * @type {Ref<number>}
79 - */
80 const currentPage = ref(0) 80 const currentPage = ref(0)
81 -
82 -/**
83 - * 每页数量
84 - * @type {number}
85 - */
86 const pageSize = 10 81 const pageSize = 10
87 -
88 -/**
89 - * 是否还有更多数据
90 - * @type {Ref<boolean>}
91 - */
92 const hasMore = ref(true) 82 const hasMore = ref(true)
83 +const loading = ref(false)
84 +const loadingMore = ref(false)
93 85
94 /** 86 /**
95 - * 首次加载状态 87 + * 提取消息标题(第一行或截取)
96 - * @type {Ref<boolean>} 88 + *
89 + * @param {string} note - 消息内容
90 + * @returns {string} 标题
97 */ 91 */
98 -const loading = ref(false) 92 +const getItemTitle = (note) => {
93 + if (!note) return '暂无消息内容'
94 +
95 + // 提取第一行作为标题
96 + const firstLine = note.split('\n')[0]
97 +
98 + // 移除富文本标签(简单处理)
99 + const textOnly = firstLine.replace(/<[^>]+>/g, '').trim()
100 +
101 + // 如果第一行太长,截取前50个字符
102 + return textOnly.length > 50 ? textOnly.substring(0, 50) + '...' : textOnly
103 +}
99 104
100 /** 105 /**
101 - * 加载更多状态 106 + * 提取消息预览(移除第一行后的内容)
102 - * @type {Ref<boolean>} 107 + *
108 + * @param {string} note - 消息内容
109 + * @returns {string} 预览内容
103 */ 110 */
104 -const loadingMore = ref(false) 111 +const getItemPreview = (note) => {
112 + if (!note) return ''
113 +
114 + // 移除第一行(已在标题显示)
115 + const lines = note.split('\n')
116 + if (lines.length > 1) {
117 + // 移除富文本标签(简单处理)
118 + const preview = lines.slice(1).join('\n').replace(/<[^>]+>/g, '').trim()
119 + return preview.substring(0, 100) // 限制预览长度
120 + }
121 +
122 + return '' // 只有一行时不显示预览
123 +}
105 124
106 /** 125 /**
107 * 获取消息列表 126 * 获取消息列表
......