hookehuyr

feat(消息页面): 重构消息页面UI并添加NutUI组件支持

- 使用NutUI组件重构消息页面布局和样式
- 添加Tab分类功能支持全部、未读、通知和留言
- 实现消息列表的滚动加载功能
- 更新全局组件声明添加NutUI的Col、Row、Tabs等组件
- 修改页面标题从"首页"改为"消息"
...@@ -9,6 +9,7 @@ declare module 'vue' { ...@@ -9,6 +9,7 @@ declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 NavBar: typeof import('./src/components/navBar.vue')['default'] 10 NavBar: typeof import('./src/components/navBar.vue')['default']
11 NutButton: typeof import('@nutui/nutui-taro')['Button'] 11 NutButton: typeof import('@nutui/nutui-taro')['Button']
12 + NutCol: typeof import('@nutui/nutui-taro')['Col']
12 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider'] 13 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
13 NutForm: typeof import('@nutui/nutui-taro')['Form'] 14 NutForm: typeof import('@nutui/nutui-taro')['Form']
14 NutFormItem: typeof import('@nutui/nutui-taro')['FormItem'] 15 NutFormItem: typeof import('@nutui/nutui-taro')['FormItem']
...@@ -19,9 +20,12 @@ declare module 'vue' { ...@@ -19,9 +20,12 @@ declare module 'vue' {
19 NutNavbar: typeof import('@nutui/nutui-taro')['Navbar'] 20 NutNavbar: typeof import('@nutui/nutui-taro')['Navbar']
20 NutPicker: typeof import('@nutui/nutui-taro')['Picker'] 21 NutPicker: typeof import('@nutui/nutui-taro')['Picker']
21 NutPopup: typeof import('@nutui/nutui-taro')['Popup'] 22 NutPopup: typeof import('@nutui/nutui-taro')['Popup']
23 + NutRow: typeof import('@nutui/nutui-taro')['Row']
22 NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar'] 24 NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar']
23 NutSwiper: typeof import('@nutui/nutui-taro')['Swiper'] 25 NutSwiper: typeof import('@nutui/nutui-taro')['Swiper']
24 NutSwiperItem: typeof import('@nutui/nutui-taro')['SwiperItem'] 26 NutSwiperItem: typeof import('@nutui/nutui-taro')['SwiperItem']
27 + NutTabPane: typeof import('@nutui/nutui-taro')['TabPane']
28 + NutTabs: typeof import('@nutui/nutui-taro')['Tabs']
25 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea'] 29 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea']
26 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 30 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
27 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 31 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
......
1 +/*
2 + * @Date: 2025-07-01 17:55:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-02 11:14:06
5 + * @FilePath: /jgdl/src/pages/messages/index.config.js
6 + * @Description: 文件描述
7 + */
1 export default { 8 export default {
2 - navigationBarTitleText: '首页' 9 + navigationBarTitleText: '消息'
3 } 10 }
......
1 <template> 1 <template>
2 <view class="messages-page"> 2 <view class="messages-page">
3 - <!-- 顶部搜索栏 --> 3 + <!-- Header -->
4 - <view class="search-container"> 4 + <view class="bg-orange-400 p-4 pt-8">
5 - <view class="search-box"> 5 + <view class="text-xl font-bold text-white mb-3">消息</view>
6 - <Search size="18" color="#9ca3af" /> 6 + <!-- Search Bar -->
7 - <input 7 + <nut-searchbar v-model="searchValue" placeholder="搜索消息" shape="round" background="transparent"
8 - v-model="searchValue" 8 + input-background="#ffffff">
9 - placeholder="搜索聊天记录..." 9 + <template #leftin>
10 - class="search-input" 10 + <Search2 />
11 - /> 11 + </template>
12 - </view> 12 + </nut-searchbar>
13 </view> 13 </view>
14 14
15 - <!-- 消息列表 --> 15 + <!-- Tab Navigation -->
16 - <view class="messages-list"> 16 + <nut-tabs v-model="activeTab" @click="onTabClick">
17 - <view 17 + <nut-tab-pane title="全部" pane-key="all">
18 - v-for="message in filteredMessages" 18 + <scroll-view class="conversation-list" scroll-y @scrolltolower="loadMore" :lower-threshold="50">
19 - :key="message.id" 19 + <view v-for="conversation in filteredConversations" :key="conversation.id" class="conversation-item"
20 - class="message-item" 20 + @click="onConversationClick(conversation)">
21 - @click="onMessageClick(message)" 21 + <nut-row>
22 - > 22 + <nut-col :span="4" class="avatar-container">
23 - <view class="avatar-container"> 23 + <view class="relative">
24 - <image :src="message.avatar" class="avatar" mode="aspectFill" /> 24 + <image v-if="conversation.avatar" :src="conversation.avatar"
25 - <view v-if="message.unreadCount > 0" class="unread-badge"> 25 + class="w-12 h-12 rounded-full object-cover" mode="aspectFill" />
26 - <text class="unread-count">{{ message.unreadCount > 99 ? '99+' : message.unreadCount }}</text> 26 + <view v-else
27 + class="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center">
28 + <component :is="conversation.icon" />
29 + </view>
30 + </view>
31 + </nut-col>
32 + <nut-col :span="20" class="content-container">
33 + <view class="flex justify-between items-center mb-1">
34 + <text class="font-medium truncate flex-1 mr-2">{{ conversation.name }}</text>
35 + <view class="flex items-center flex-shrink-0">
36 + <text class="text-xs text-gray-500">{{ conversation.time }}</text>
37 + <view v-if="conversation.unread" class="ml-1 w-2 h-2 bg-red-500 rounded-full">
38 + </view>
39 + </view>
40 + </view>
41 + <text class="text-sm text-gray-600 truncate block">{{ conversation.lastMessage }}</text>
42 + </nut-col>
43 + </nut-row>
27 </view> 44 </view>
28 - </view> 45 +
29 - 46 + <!-- Loading indicator -->
30 - <view class="message-content"> 47 + <view v-if="loading" class="loading-container">
31 - <view class="message-header"> 48 + <text class="loading-text">加载中...</text>
32 - <text class="sender-name">{{ message.senderName }}</text>
33 - <text class="message-time">{{ formatTime(message.timestamp) }}</text>
34 </view> 49 </view>
35 - 50 + </scroll-view>
36 - <view class="message-preview"> 51 + </nut-tab-pane>
37 - <text class="preview-text" :class="{ 'unread': message.unreadCount > 0 }"> 52 +
38 - {{ message.lastMessage }} 53 + <nut-tab-pane title="未读" pane-key="unread">
39 - </text> 54 + <scroll-view class="conversation-list" scroll-y @scrolltolower="loadMore" :lower-threshold="50">
40 - <view v-if="message.type === 'image'" class="message-type-icon"> 55 + <view v-for="conversation in filteredConversations" :key="conversation.id" class="conversation-item"
41 - <Image size="16" color="#9ca3af" /> 56 + @click="onConversationClick(conversation)">
42 - </view> 57 + <nut-row>
58 + <nut-col :span="4" class="avatar-container">
59 + <view class="relative">
60 + <image v-if="conversation.avatar" :src="conversation.avatar"
61 + class="w-12 h-12 rounded-full object-cover" mode="aspectFill" />
62 + <view v-else
63 + class="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center">
64 + <component :is="conversation.icon" />
65 + </view>
66 + </view>
67 + </nut-col>
68 + <nut-col :span="20" class="content-container">
69 + <view class="flex justify-between items-center mb-1">
70 + <text class="font-medium truncate flex-1 mr-2">{{ conversation.name }}</text>
71 + <view class="flex items-center flex-shrink-0">
72 + <text class="text-xs text-gray-500">{{ conversation.time }}</text>
73 + <view v-if="conversation.unread" class="ml-1 w-2 h-2 bg-red-500 rounded-full">
74 + </view>
75 + </view>
76 + </view>
77 + <text class="text-sm text-gray-600 truncate block">{{ conversation.lastMessage }}</text>
78 + </nut-col>
79 + </nut-row>
43 </view> 80 </view>
44 - </view>
45 - </view>
46 - </view>
47 81
48 - <!-- 空状态 --> 82 + <!-- Loading indicator -->
49 - <view v-if="filteredMessages.length === 0" class="empty-state"> 83 + <view v-if="loading" class="loading-container">
50 - <view class="empty-icon"> 84 + <text class="loading-text">加载中...</text>
51 - <Message size="48" color="#d1d5db" /> 85 + </view>
52 - </view> 86 + </scroll-view>
53 - <text class="empty-title">暂无消息</text> 87 + </nut-tab-pane>
54 - <text class="empty-subtitle">开始与买家或卖家聊天吧</text> 88 +
55 - </view> 89 + <nut-tab-pane title="通知" pane-key="notification">
90 + <scroll-view class="conversation-list" scroll-y @scrolltolower="loadMore" :lower-threshold="50">
91 + <view v-for="conversation in filteredConversations" :key="conversation.id" class="conversation-item"
92 + @click="onConversationClick(conversation)">
93 + <nut-row>
94 + <nut-col :span="4" class="avatar-container">
95 + <view class="relative">
96 + <image v-if="conversation.avatar" :src="conversation.avatar"
97 + class="w-12 h-12 rounded-full object-cover" mode="aspectFill" />
98 + <view v-else
99 + class="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center">
100 + <component :is="conversation.icon" />
101 + </view>
102 + </view>
103 + </nut-col>
104 + <nut-col :span="20" class="content-container">
105 + <view class="flex justify-between items-center mb-1">
106 + <text class="font-medium truncate flex-1 mr-2">{{ conversation.name }}</text>
107 + <view class="flex items-center flex-shrink-0">
108 + <text class="text-xs text-gray-500">{{ conversation.time }}</text>
109 + <view v-if="conversation.unread" class="ml-1 w-2 h-2 bg-red-500 rounded-full">
110 + </view>
111 + </view>
112 + </view>
113 + <text class="text-sm text-gray-600 truncate block">{{ conversation.lastMessage }}</text>
114 + </nut-col>
115 + </nut-row>
116 + </view>
117 +
118 + <!-- Loading indicator -->
119 + <view v-if="loading" class="loading-container">
120 + <text class="loading-text">加载中...</text>
121 + </view>
122 + </scroll-view>
123 + </nut-tab-pane>
124 +
125 + <nut-tab-pane title="留言" pane-key="message">
126 + <scroll-view class="conversation-list" scroll-y @scrolltolower="loadMore" :lower-threshold="50">
127 + <view v-for="conversation in filteredConversations" :key="conversation.id" class="conversation-item"
128 + @click="onConversationClick(conversation)">
129 + <nut-row>
130 + <nut-col :span="4" class="avatar-container">
131 + <view class="relative">
132 + <image v-if="conversation.avatar" :src="conversation.avatar"
133 + class="w-12 h-12 rounded-full object-cover" mode="aspectFill" />
134 + <view v-else
135 + class="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center">
136 + <component :is="conversation.icon" />
137 + </view>
138 + </view>
139 + </nut-col>
140 + <nut-col :span="20" class="content-container">
141 + <view class="flex justify-between items-center mb-1">
142 + <text class="font-medium truncate flex-1 mr-2">{{ conversation.name }}</text>
143 + <view class="flex items-center flex-shrink-0">
144 + <text class="text-xs text-gray-500">{{ conversation.time }}</text>
145 + <view v-if="conversation.unread" class="ml-1 w-2 h-2 bg-red-500 rounded-full">
146 + </view>
147 + </view>
148 + </view>
149 + <text class="text-sm text-gray-600 truncate block">{{ conversation.lastMessage }}</text>
150 + </nut-col>
151 + </nut-row>
152 + </view>
153 +
154 + <!-- Loading indicator -->
155 + <view v-if="loading" class="loading-container">
156 + <text class="loading-text">加载中...</text>
157 + </view>
158 + </scroll-view>
159 + </nut-tab-pane>
160 + </nut-tabs>
56 161
57 - <!-- 浮动按钮 -->
58 - <view class="floating-btn" @click="onNewMessage">
59 - <Plus size="24" color="#ffffff" />
60 - </view>
61 -
62 <!-- 自定义TabBar --> 162 <!-- 自定义TabBar -->
63 <TabBar /> 163 <TabBar />
64 </view> 164 </view>
65 </template> 165 </template>
66 166
67 <script setup> 167 <script setup>
68 -import { ref, computed } from 'vue' 168 +import { ref, computed, onMounted, markRaw } from 'vue'
69 -import { Search, Message, Plus, Image } from '@nutui/icons-vue-taro'
70 import Taro from '@tarojs/taro' 169 import Taro from '@tarojs/taro'
170 +import { Search2, Notice, Message } from '@nutui/icons-vue-taro'
71 import TabBar from '@/components/TabBar.vue' 171 import TabBar from '@/components/TabBar.vue'
72 172
73 -// 响应式数据 173 +// 搜索值
74 const searchValue = ref('') 174 const searchValue = ref('')
175 +// 当前激活的Tab
176 +const activeTab = ref('all')
177 +// 加载状态
178 +const loading = ref(false)
179 +// 页码
180 +const page = ref(1)
181 +// 每页数量
182 +const pageSize = 10
183 +// 是否还有更多数据
184 +const hasMore = ref(true)
185 +
186 +// 模拟对话数据
187 +const conversations = ref([])
188 +
189 +// 初始化数据
190 +const initData = () => {
191 + const mockData = [
192 + {
193 + id: 1,
194 + name: '张三',
195 + avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
196 + lastMessage: '你好,这个商品还在吗?',
197 + time: '5分钟前',
198 + unread: true,
199 + type: 'chat'
200 + },
201 + {
202 + id: 2,
203 + name: '李四',
204 + avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
205 + lastMessage: '价格可以商量吗?',
206 + time: '30分钟前',
207 + unread: false,
208 + type: 'chat'
209 + },
210 + {
211 + id: 3,
212 + name: '系统通知',
213 + avatar: '',
214 + icon: markRaw(Notice),
215 + lastMessage: '您的商品已通过审核',
216 + time: '1小时前',
217 + unread: true,
218 + type: 'notification'
219 + },
220 + {
221 + id: 4,
222 + name: '王五',
223 + avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
224 + lastMessage: '[图片]',
225 + time: '2小时前',
226 + unread: true,
227 + type: 'chat'
228 + },
229 + {
230 + id: 5,
231 + name: '客服留言',
232 + avatar: '',
233 + icon: markRaw(Message),
234 + lastMessage: '感谢您的反馈,我们会尽快处理',
235 + time: '昨天',
236 + unread: false,
237 + type: 'message'
238 + }
239 + ]
240 +
241 + conversations.value = mockData
242 +}
75 243
76 -// 消息数据 244 +// 过滤后的对话列表
77 -const messages = ref([ 245 +const filteredConversations = computed(() => {
78 - { 246 + let filtered = conversations.value
79 - id: 1, 247 +
80 - senderName: '张同学', 248 + // 根据Tab过滤
81 - avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face', 249 + if (activeTab.value === 'unread') {
82 - lastMessage: '这辆车还在吗?可以看看实物吗?', 250 + filtered = filtered.filter(conv => conv.unread)
83 - timestamp: Date.now() - 300000, // 5分钟前 251 + } else if (activeTab.value === 'notification') {
84 - unreadCount: 2, 252 + filtered = filtered.filter(conv => conv.type === 'notification')
85 - type: 'text' 253 + } else if (activeTab.value === 'message') {
86 - }, 254 + filtered = filtered.filter(conv => conv.type === 'message')
87 - {
88 - id: 2,
89 - senderName: '李小明',
90 - avatar: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?w=100&h=100&fit=crop&crop=face',
91 - lastMessage: '价格还能再便宜点吗?',
92 - timestamp: Date.now() - 1800000, // 30分钟前
93 - unreadCount: 0,
94 - type: 'text'
95 - },
96 - {
97 - id: 3,
98 - senderName: '王美丽',
99 - avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face',
100 - lastMessage: '[图片]',
101 - timestamp: Date.now() - 3600000, // 1小时前
102 - unreadCount: 1,
103 - type: 'image'
104 - },
105 - {
106 - id: 4,
107 - senderName: '陈大华',
108 - avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face',
109 - lastMessage: '好的,谢谢!',
110 - timestamp: Date.now() - 7200000, // 2小时前
111 - unreadCount: 0,
112 - type: 'text'
113 - },
114 - {
115 - id: 5,
116 - senderName: '刘小红',
117 - avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face',
118 - lastMessage: '车子的电池还好用吗?大概能跑多远?',
119 - timestamp: Date.now() - 86400000, // 1天前
120 - unreadCount: 0,
121 - type: 'text'
122 - },
123 - {
124 - id: 6,
125 - senderName: '赵强',
126 - avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face',
127 - lastMessage: '明天下午有时间看车吗?',
128 - timestamp: Date.now() - 172800000, // 2天前
129 - unreadCount: 0,
130 - type: 'text'
131 } 255 }
132 -])
133 256
134 -// 过滤后的消息列表 257 + // 根据搜索关键词过滤
135 -const filteredMessages = computed(() => { 258 + if (searchValue.value) {
136 - if (!searchValue.value.trim()) { 259 + filtered = filtered.filter(conv =>
137 - return messages.value 260 + conv.name.includes(searchValue.value) ||
261 + conv.lastMessage.includes(searchValue.value)
262 + )
138 } 263 }
139 - 264 +
140 - return messages.value.filter(message => 265 + return filtered
141 - message.senderName.includes(searchValue.value) ||
142 - message.lastMessage.includes(searchValue.value)
143 - )
144 }) 266 })
145 267
146 -/** 268 +// Tab点击事件
147 - * 格式化时间 269 +const onTabClick = (tab) => {
148 - * @param {number} timestamp - 时间戳 270 + activeTab.value = tab.paneKey
149 - * @returns {string} 格式化后的时间 271 + // 重置分页
150 - */ 272 + page.value = 1
151 -const formatTime = (timestamp) => { 273 + hasMore.value = true
152 - const now = Date.now()
153 - const diff = now - timestamp
154 -
155 - if (diff < 60000) { // 1分钟内
156 - return '刚刚'
157 - } else if (diff < 3600000) { // 1小时内
158 - return `${Math.floor(diff / 60000)}分钟前`
159 - } else if (diff < 86400000) { // 1天内
160 - return `${Math.floor(diff / 3600000)}小时前`
161 - } else if (diff < 604800000) { // 1周内
162 - return `${Math.floor(diff / 86400000)}天前`
163 - } else {
164 - const date = new Date(timestamp)
165 - return `${date.getMonth() + 1}/${date.getDate()}`
166 - }
167 } 274 }
168 275
169 -/** 276 +// 加载更多数据
170 - * 消息点击事件 277 +const loadMore = async () => {
171 - * @param {object} message - 消息对象 278 + if (loading.value || !hasMore.value) return
172 - */ 279 +
173 -const onMessageClick = (message) => { 280 + loading.value = true
174 - // 清除未读数量 281 +
175 - message.unreadCount = 0 282 + // 模拟API请求
176 - 283 + setTimeout(() => {
177 - // 跳转到聊天详情页面 284 + const newData = generateMockData(page.value + 1)
178 - Taro.navigateTo({ 285 + if (newData.length > 0) {
179 - url: `/pages/chat/index?userId=${message.id}&userName=${message.senderName}` 286 + conversations.value.push(...newData)
180 - }) 287 + page.value++
288 + } else {
289 + hasMore.value = false
290 + }
291 + loading.value = false
292 + }, 1000)
181 } 293 }
182 294
183 -/** 295 +// 生成模拟数据
184 - * 新建消息 296 +const generateMockData = (pageNum) => {
185 - */ 297 + if (pageNum > 3) return [] // 模拟只有3页数据
186 -const onNewMessage = async () => { 298 +
187 - try { 299 + const mockData = []
188 - await Taro.showToast({ 300 + const startId = (pageNum - 1) * pageSize + conversations.value.length + 1
189 - title: '新建消息', 301 +
190 - icon: 'none' 302 + for (let i = 0; i < pageSize; i++) {
303 + mockData.push({
304 + id: startId + i,
305 + name: `用户${startId + i}`,
306 + avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
307 + lastMessage: `这是第${startId + i}条消息`,
308 + time: `${Math.floor(Math.random() * 24)}小时前`,
309 + unread: Math.random() > 0.5,
310 + type: 'chat'
191 }) 311 })
192 - } catch (error) {
193 - console.error('新建消息失败:', error)
194 } 312 }
313 +
314 + return mockData
315 +}
316 +
317 +// 点击对话
318 +const onConversationClick = (conversation) => {
319 + // 跳转到聊天页面
320 + Taro.navigateTo({
321 + url: `/pages/chat/index?id=${conversation.id}&name=${conversation.name}`
322 + })
195 } 323 }
324 +
325 +// 页面加载时初始化数据
326 +onMounted(() => {
327 + initData()
328 +})
196 </script> 329 </script>
197 330
198 -<style lang="less"> 331 +<style scoped>
199 .messages-page { 332 .messages-page {
200 min-height: 100vh; 333 min-height: 100vh;
201 - background-color: #f9fafb; 334 + background-color: #f5f5f5;
202 - padding-bottom: 100px; 335 + padding-bottom: 100rpx;
336 + /* 为TabBar留出空间 */
203 } 337 }
204 338
205 -.search-container { 339 +/* 对话列表样式 */
206 - padding: 16px; 340 +.conversation-list {
207 - background-color: #ffffff; 341 + height: calc(100vh - 300rpx);
208 - border-bottom: 1px solid #f3f4f6; 342 + background: #ffffff;
209 } 343 }
210 344
211 -.search-box { 345 +.conversation-item {
346 + padding: 24rpx 20rpx;
347 + border-bottom: 1rpx solid #f0f0f0;
348 + transition: background-color 0.2s;
349 +}
350 +
351 +.avatar-container {
212 display: flex; 352 display: flex;
213 - align-items: center; 353 + align-items: flex-start;
214 - background-color: #f9fafb; 354 + width: 88rpx;
215 - border-radius: 24px; 355 + margin-right: 24rpx;
216 - padding: 12px 16px;
217 - gap: 8px;
218 } 356 }
219 357
220 -.search-input { 358 +.content-container {
221 - flex: 1; 359 + padding-left: 8px;
222 - border: none; 360 +}
223 - outline: none; 361 +
224 - background: transparent; 362 +.conversation-item:active {
225 - font-size: 14px; 363 + background-color: #f8f9fa;
226 - color: #374151;
227 } 364 }
228 365
229 -.messages-list { 366 +.conversation-item:last-child {
230 - background-color: #ffffff; 367 + border-bottom: none;
231 } 368 }
232 369
233 -.message-item { 370 +/* 加载指示器 */
371 +.loading-container {
234 display: flex; 372 display: flex;
235 align-items: center; 373 align-items: center;
236 - padding: 16px; 374 + justify-content: center;
237 - border-bottom: 1px solid #f3f4f6; 375 + padding: 40rpx;
238 - transition: background-color 0.2s; 376 + gap: 16rpx;
239 } 377 }
240 378
241 -.message-item:active { 379 +.loading-text {
242 - background-color: #f9fafb; 380 + font-size: 28rpx;
381 + color: #9ca3af;
243 } 382 }
244 383
245 -.message-item:last-child { 384 +/* Tailwind CSS 类的补充样式 */
246 - border-bottom: none; 385 +.w-12 {
386 + width: 88rpx;
247 } 387 }
248 388
249 -.avatar-container { 389 +.h-12 {
250 - position: relative; 390 + height: 88rpx;
251 - margin-right: 12px;
252 } 391 }
253 392
254 -.avatar { 393 +.w-2 {
255 - width: 48px; 394 + width: 8rpx;
256 - height: 48px; 395 +}
396 +
397 +.h-2 {
398 + height: 8rpx;
399 +}
400 +
401 +.rounded-full {
257 border-radius: 50%; 402 border-radius: 50%;
403 +}
404 +
405 +.object-cover {
258 object-fit: cover; 406 object-fit: cover;
259 } 407 }
260 408
261 -.unread-badge { 409 +.bg-gray-100 {
262 - position: absolute; 410 + background-color: #f3f4f6;
263 - top: -4px; 411 +}
264 - right: -4px; 412 +
265 - min-width: 20px; 413 +.bg-red-500 {
266 - height: 20px;
267 background-color: #ef4444; 414 background-color: #ef4444;
268 - border-radius: 10px;
269 - display: flex;
270 - align-items: center;
271 - justify-content: center;
272 - border: 2px solid #ffffff;
273 } 415 }
274 416
275 -.unread-count { 417 +.flex {
276 - font-size: 12px; 418 + display: flex;
277 - color: #ffffff;
278 - font-weight: 500;
279 - line-height: 1;
280 } 419 }
281 420
282 -.message-content { 421 +.flex-1 {
283 flex: 1; 422 flex: 1;
423 +}
424 +
425 +.flex-shrink-0 {
426 + flex-shrink: 0;
427 +}
428 +
429 +.min-w-0 {
284 min-width: 0; 430 min-width: 0;
285 } 431 }
286 432
287 -.message-header { 433 +.items-center {
288 - display: flex;
289 - justify-content: space-between;
290 align-items: center; 434 align-items: center;
291 - margin-bottom: 4px;
292 } 435 }
293 436
294 -.sender-name { 437 +.justify-center {
295 - font-size: 16px; 438 + justify-content: center;
439 +}
440 +
441 +.justify-between {
442 + justify-content: space-between;
443 +}
444 +
445 +.ml-1 {
446 + margin-left: 4rpx;
447 +}
448 +
449 +.ml-3 {
450 + margin-left: 24rpx;
451 +}
452 +
453 +.mt-1 {
454 + margin-top: 4rpx;
455 +}
456 +
457 +.font-medium {
296 font-weight: 500; 458 font-weight: 500;
297 - color: #111827;
298 } 459 }
299 460
300 -.message-time { 461 +.text-xs {
301 - font-size: 12px; 462 + font-size: 24rpx;
302 - color: #9ca3af;
303 } 463 }
304 464
305 -.message-preview { 465 +.text-sm {
306 - display: flex; 466 + font-size: 28rpx;
307 - align-items: center;
308 - gap: 4px;
309 } 467 }
310 468
311 -.preview-text { 469 +.text-xl {
312 - flex: 1; 470 + font-size: 40rpx;
313 - font-size: 14px; 471 +}
472 +
473 +.font-bold {
474 + font-weight: bold;
475 +}
476 +
477 +.text-white {
478 + color: #ffffff;
479 +}
480 +
481 +.text-gray-500 {
314 color: #6b7280; 482 color: #6b7280;
483 +}
484 +
485 +.text-gray-600 {
486 + color: #4b5563;
487 +}
488 +
489 +.bg-orange-400 {
490 + background-color: #fb923c;
491 +}
492 +
493 +.p-4 {
494 + padding: 32rpx;
495 +}
496 +
497 +.pt-8 {
498 + padding-top: 64rpx;
499 +}
500 +
501 +.mb-1 {
502 + margin-bottom: 4rpx;
503 +}
504 +
505 +.mb-3 {
506 + margin-bottom: 24rpx;
507 +}
508 +
509 +.mr-2 {
510 + margin-right: 8rpx;
511 +}
512 +
513 +.truncate {
315 overflow: hidden; 514 overflow: hidden;
316 text-overflow: ellipsis; 515 text-overflow: ellipsis;
317 white-space: nowrap; 516 white-space: nowrap;
318 } 517 }
319 518
320 -.preview-text.unread { 519 +.block {
321 - color: #374151; 520 + display: block;
322 - font-weight: 500;
323 } 521 }
324 522
325 -.message-type-icon { 523 +.relative {
326 - flex-shrink: 0; 524 + position: relative;
327 } 525 }
328 526
329 -.empty-state { 527 +/* NutUI 组件样式覆盖 */
330 - display: flex; 528 +:deep(.nut-searchbar) {
331 - flex-direction: column; 529 + background: transparent !important;
332 - align-items: center;
333 - justify-content: center;
334 - padding: 80px 20px;
335 - text-align: center;
336 } 530 }
337 531
338 -.empty-icon { 532 +:deep(.nut-searchbar .nut-searchbar__search-input) {
339 - margin-bottom: 16px; 533 + background: #ffffff !important;
534 + border-radius: 40rpx !important;
340 } 535 }
341 536
342 -.empty-title { 537 +:deep(.nut-tabs) {
343 - font-size: 18px; 538 + background: #ffffff;
344 - font-weight: 500;
345 - color: #374151;
346 - margin-bottom: 8px;
347 - display: block;
348 } 539 }
349 540
350 -.empty-subtitle { 541 +:deep(.nut-tabs__titles) {
351 - font-size: 14px; 542 + background: #ffffff;
352 - color: #9ca3af; 543 + border-bottom: 1rpx solid #f0f0f0;
353 - display: block;
354 } 544 }
355 545
356 -.floating-btn { 546 +:deep(.nut-tab-pane) {
357 - position: fixed; 547 + padding: 0;
358 - bottom: 100px;
359 - right: 20px;
360 - width: 56px;
361 - height: 56px;
362 - background-color: #f97316;
363 - border-radius: 50%;
364 - display: flex;
365 - align-items: center;
366 - justify-content: center;
367 - box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
368 - transition: all 0.2s;
369 } 548 }
370 549
371 -.floating-btn:active { 550 +/* 响应式设计 */
372 - transform: scale(0.95); 551 +@media (max-width: 750rpx) {
373 - box-shadow: 0 2px 8px rgba(249, 115, 22, 0.4); 552 + .conversation-item {
553 + padding: 20rpx 16rpx;
554 + }
555 +
556 + .w-12 {
557 + width: 80rpx;
558 + }
559 +
560 + .h-12 {
561 + height: 80rpx;
562 + }
563 +
564 + .text-sm {
565 + font-size: 26rpx;
566 + }
374 } 567 }
375 -</style>
...\ No newline at end of file ...\ No newline at end of file
568 +</style>
......