hookehuyr

feat(消息): 新增消息详情组件并重构消息页面

将消息详情弹框抽离为独立组件 MessageDetail,支持消息发送功能
重构消息页面代码,移除冗余样式,优化消息列表布局
...@@ -10,6 +10,7 @@ declare module 'vue' { ...@@ -10,6 +10,7 @@ declare module 'vue' {
10 BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default'] 10 BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default']
11 FeaturedRecommendations: typeof import('./src/components/FeaturedRecommendations.vue')['default'] 11 FeaturedRecommendations: typeof import('./src/components/FeaturedRecommendations.vue')['default']
12 LatestScooters: typeof import('./src/components/LatestScooters.vue')['default'] 12 LatestScooters: typeof import('./src/components/LatestScooters.vue')['default']
13 + MessageDetail: typeof import('./src/components/MessageDetail.vue')['default']
13 NavBar: typeof import('./src/components/navBar.vue')['default'] 14 NavBar: typeof import('./src/components/navBar.vue')['default']
14 NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet'] 15 NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
15 NutButton: typeof import('@nutui/nutui-taro')['Button'] 16 NutButton: typeof import('@nutui/nutui-taro')['Button']
......
This diff is collapsed. Click to expand it.
...@@ -40,8 +40,9 @@ ...@@ -40,8 +40,9 @@
40 </nut-sticky> 40 </nut-sticky>
41 41
42 <!-- 消息列表内容 --> 42 <!-- 消息列表内容 -->
43 - <scroll-view ref="scrollViewRef" class="conversation-list" :style="scrollStyle" :scroll-y="true" @scrolltolower="loadMore" 43 + <scroll-view ref="scrollViewRef" class="conversation-list" :style="scrollStyle" :scroll-y="true"
44 - @scroll="scroll" :lower-threshold="100" :enable-flex="false" :scroll-top="scrollTop"> 44 + @scrolltolower="loadMore" @scroll="scroll" :lower-threshold="100" :enable-flex="false"
45 + :scroll-top="scrollTop">
45 <view v-for="conversation in filteredConversations" :key="conversation.id" 46 <view v-for="conversation in filteredConversations" :key="conversation.id"
46 class="conversation-item mt-2 mb-4 border-b border-gray-100 pb-2" 47 class="conversation-item mt-2 mb-4 border-b border-gray-100 pb-2"
47 @click="onConversationClick(conversation)"> 48 @click="onConversationClick(conversation)">
...@@ -70,7 +71,8 @@ ...@@ -70,7 +71,8 @@
70 </view> 71 </view>
71 72
72 <!-- 空状态提示 --> 73 <!-- 空状态提示 -->
73 - <view v-if="filteredConversations.length === 0 && !loading && !hasMore" class="empty-state py-8 text-center"> 74 + <view v-if="filteredConversations.length === 0 && !loading && !hasMore"
75 + class="empty-state py-8 text-center">
74 <Message size="48" color="#9ca3af" class="mb-4" /> 76 <Message size="48" color="#9ca3af" class="mb-4" />
75 <text class="text-gray-500 text-base block mb-2">暂无消息</text> 77 <text class="text-gray-500 text-base block mb-2">暂无消息</text>
76 <text class="text-gray-400 text-sm">当前筛选条件下没有找到相关消息</text> 78 <text class="text-gray-400 text-sm">当前筛选条件下没有找到相关消息</text>
...@@ -91,39 +93,8 @@ ...@@ -91,39 +93,8 @@
91 <TabBar /> 93 <TabBar />
92 94
93 <!-- 消息详情弹框 --> 95 <!-- 消息详情弹框 -->
94 - <nut-popup v-model:visible="showMessageDetail" position="right" :style="{ width: '100%', height: '100%' }" 96 + <MessageDetail v-model="showMessageDetail" :conversation="selectedConversation" @close="closeMessageDetail"
95 - closeable close-icon-position="top-right" @close="closeMessageDetail"> 97 + @sendMessage="handleSendMessage" />
96 - <view class="message-detail-container">
97 - <!-- 详情页头部 -->
98 - <view class="detail-header">
99 - <view class="flex items-center">
100 - <image v-if="selectedConversation?.avatar" :src="selectedConversation.avatar"
101 - class="w-12 h-12 rounded-full object-cover mr-3" mode="aspectFill" />
102 - <view v-else class="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center mr-3">
103 - <component :is="selectedConversation?.icon" />
104 - </view>
105 - <view class="flex-1">
106 - <text class="text-lg font-medium">{{ selectedConversation?.name }}</text>
107 - <text class="text-sm text-gray-500 block">{{ selectedConversation?.time }}</text>
108 - </view>
109 - </view>
110 - </view>
111 -
112 - <!-- 消息内容 -->
113 - <view class="detail-content">
114 - <view class="message-content">
115 - <text class="text-base">{{ selectedConversation?.lastMessage }}</text>
116 - </view>
117 - </view>
118 -
119 - <!-- 底部关闭按钮 -->
120 - <view class="detail-footer">
121 - <nut-button type="primary" size="large" block @click="closeMessageDetail" color="orange">
122 - 关闭
123 - </nut-button>
124 - </view>
125 - </view>
126 - </nut-popup>
127 </view> 98 </view>
128 </template> 99 </template>
129 100
...@@ -131,10 +102,12 @@ ...@@ -131,10 +102,12 @@
131 import { ref, computed, onMounted, markRaw } from 'vue' 102 import { ref, computed, onMounted, markRaw } from 'vue'
132 import { Search2, Notice, Message } from '@nutui/icons-vue-taro' 103 import { Search2, Notice, Message } from '@nutui/icons-vue-taro'
133 import TabBar from '@/components/TabBar.vue' 104 import TabBar from '@/components/TabBar.vue'
105 +import MessageDetail from '@/components/MessageDetail.vue'
134 import { $ } from '@tarojs/extend' 106 import { $ } from '@tarojs/extend'
107 +import Taro from '@tarojs/taro'
135 108
136 // 默认头像 109 // 默认头像
137 -const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 110 +// const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
138 111
139 const scrollStyle = ref({ 112 const scrollStyle = ref({
140 height: 'calc(100vh - 500rpx)' 113 height: 'calc(100vh - 500rpx)'
...@@ -329,6 +302,25 @@ const markAsRead = (conversationId) => { ...@@ -329,6 +302,25 @@ const markAsRead = (conversationId) => {
329 } 302 }
330 } 303 }
331 304
305 +// 处理发送消息
306 +const handleSendMessage = (data) => {
307 + const { conversation, message } = data
308 + if (!conversation || !message.trim()) return
309 +
310 + // 更新对话的最后一条消息
311 + const conv = conversations.value.find(conv => conv.id === conversation.id)
312 + if (conv) {
313 + conv.lastMessage = message
314 + conv.time = '刚刚'
315 + conv.unread = false // 标记为已读
316 + }
317 +
318 + Taro.showToast({
319 + title: '消息发送成功',
320 + icon: 'success'
321 + })
322 +}
323 +
332 // 页面加载时初始化数据 324 // 页面加载时初始化数据
333 onMounted(() => { 325 onMounted(() => {
334 // 设置滚动列表可视高度 326 // 设置滚动列表可视高度
...@@ -560,69 +552,7 @@ onMounted(() => { ...@@ -560,69 +552,7 @@ onMounted(() => {
560 position: relative; 552 position: relative;
561 } 553 }
562 554
563 - /* 消息详情弹框样式 */
564 - .message-detail-container {
565 - height: 100%;
566 - display: flex;
567 - flex-direction: column;
568 - background: #ffffff;
569 - }
570 555
571 - .detail-header {
572 - padding: 32rpx 24rpx;
573 - border-bottom: 1rpx solid #f0f0f0;
574 - background: #ffffff;
575 - flex-shrink: 0;
576 - }
577 -
578 - .detail-content {
579 - flex: 1;
580 - padding: 24rpx;
581 - overflow-y: auto;
582 - background: #f8f9fa;
583 - }
584 -
585 - .message-content {
586 - background: #ffffff;
587 - padding: 24rpx;
588 - border-radius: 16rpx;
589 - margin-bottom: 24rpx;
590 - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
591 - }
592 -
593 - .message-history {
594 - .message-item {
595 - background: #ffffff;
596 - padding: 20rpx;
597 - border-radius: 12rpx;
598 - margin-bottom: 16rpx;
599 - box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.05);
600 - }
601 - }
602 -
603 - .detail-footer {
604 - padding: 24rpx;
605 - background: #ffffff;
606 - border-top: 1rpx solid #f0f0f0;
607 - flex-shrink: 0;
608 - }
609 -
610 - .text-lg {
611 - font-size: 36rpx;
612 - }
613 -
614 - .text-base {
615 - font-size: 32rpx;
616 - line-height: 1.5;
617 - }
618 -
619 - .mr-3 {
620 - margin-right: 24rpx;
621 - }
622 -
623 - .mt-1 {
624 - margin-top: 8rpx;
625 - }
626 556
627 } 557 }
628 558
...@@ -659,7 +589,8 @@ onMounted(() => { ...@@ -659,7 +589,8 @@ onMounted(() => {
659 /* 状态筛选标签 */ 589 /* 状态筛选标签 */
660 .status-tabs { 590 .status-tabs {
661 background: white; 591 background: white;
662 - padding: 20rpx 35rpx; /* 增加内边距 */ 592 + padding: 20rpx 35rpx;
593 + /* 增加内边距 */
663 border-bottom: 1rpx solid #e5e7eb; 594 border-bottom: 1rpx solid #e5e7eb;
664 display: flex; 595 display: flex;
665 position: relative; 596 position: relative;
...@@ -668,14 +599,15 @@ onMounted(() => { ...@@ -668,14 +599,15 @@ onMounted(() => {
668 -ms-overflow-style: none; 599 -ms-overflow-style: none;
669 600
670 &::-webkit-scrollbar { 601 &::-webkit-scrollbar {
671 - display: none; 602 + display: none;
672 } 603 }
673 } 604 }
674 605
675 .tab-item { 606 .tab-item {
676 margin-right: 48rpx; 607 margin-right: 48rpx;
677 padding-bottom: 16rpx; 608 padding-bottom: 16rpx;
678 - font-size: 30rpx; /* 增大字体 */ 609 + font-size: 30rpx;
610 + /* 增大字体 */
679 color: #6b7280; 611 color: #6b7280;
680 position: relative; 612 position: relative;
681 cursor: pointer; 613 cursor: pointer;
...@@ -685,30 +617,30 @@ onMounted(() => { ...@@ -685,30 +617,30 @@ onMounted(() => {
685 transform: translateX(0); 617 transform: translateX(0);
686 618
687 &.active { 619 &.active {
688 - color: #f97316; 620 + color: #f97316;
689 - font-weight: 500; 621 + font-weight: 500;
690 - transform: translateY(-2rpx); 622 + transform: translateY(-2rpx);
691 - 623 +
692 - &::after { 624 + &::after {
693 - content: ''; 625 + content: '';
694 - position: absolute; 626 + position: absolute;
695 - bottom: 0; 627 + bottom: 0;
696 - left: 0; 628 + left: 0;
697 - right: 0; 629 + right: 0;
698 - height: 4rpx; 630 + height: 4rpx;
699 - background: linear-gradient(90deg, #f97316, #fb923c); 631 + background: linear-gradient(90deg, #f97316, #fb923c);
700 - border-radius: 2rpx; 632 + border-radius: 2rpx;
701 - animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); 633 + animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
702 - } 634 + }
703 } 635 }
704 636
705 &:hover { 637 &:hover {
706 - color: #f97316; 638 + color: #f97316;
707 - transform: translateY(-1rpx); 639 + transform: translateY(-1rpx);
708 } 640 }
709 641
710 &:last-child { 642 &:last-child {
711 - margin-right: 0; 643 + margin-right: 0;
712 } 644 }
713 } 645 }
714 646
...@@ -720,6 +652,7 @@ onMounted(() => { ...@@ -720,6 +652,7 @@ onMounted(() => {
720 transform: scaleX(0); 652 transform: scaleX(0);
721 opacity: 0; 653 opacity: 0;
722 } 654 }
655 +
723 100% { 656 100% {
724 transform: scaleX(1); 657 transform: scaleX(1);
725 opacity: 1; 658 opacity: 1;
...@@ -737,6 +670,7 @@ onMounted(() => { ...@@ -737,6 +670,7 @@ onMounted(() => {
737 opacity: 0; 670 opacity: 0;
738 transform: translateY(20rpx); 671 transform: translateY(20rpx);
739 } 672 }
673 +
740 to { 674 to {
741 opacity: 1; 675 opacity: 1;
742 transform: translateY(0); 676 transform: translateY(0);
...@@ -749,17 +683,32 @@ onMounted(() => { ...@@ -749,17 +683,32 @@ onMounted(() => {
749 animation-fill-mode: both; 683 animation-fill-mode: both;
750 } 684 }
751 685
752 -.conversation-item:nth-child(1) { animation-delay: 0.1s; } 686 +.conversation-item:nth-child(1) {
753 -.conversation-item:nth-child(2) { animation-delay: 0.15s; } 687 + animation-delay: 0.1s;
754 -.conversation-item:nth-child(3) { animation-delay: 0.2s; } 688 +}
755 -.conversation-item:nth-child(4) { animation-delay: 0.25s; } 689 +
756 -.conversation-item:nth-child(5) { animation-delay: 0.3s; } 690 +.conversation-item:nth-child(2) {
691 + animation-delay: 0.15s;
692 +}
693 +
694 +.conversation-item:nth-child(3) {
695 + animation-delay: 0.2s;
696 +}
697 +
698 +.conversation-item:nth-child(4) {
699 + animation-delay: 0.25s;
700 +}
701 +
702 +.conversation-item:nth-child(5) {
703 + animation-delay: 0.3s;
704 +}
757 705
758 @keyframes fadeInItem { 706 @keyframes fadeInItem {
759 from { 707 from {
760 opacity: 0; 708 opacity: 0;
761 transform: translateX(-20rpx); 709 transform: translateX(-20rpx);
762 } 710 }
711 +
763 to { 712 to {
764 opacity: 1; 713 opacity: 1;
765 transform: translateX(0); 714 transform: translateX(0);
......