feat(消息): 新增消息详情组件并重构消息页面
将消息详情弹框抽离为独立组件 MessageDetail,支持消息发送功能 重构消息页面代码,移除冗余样式,优化消息列表布局
Showing
3 changed files
with
56 additions
and
106 deletions
| ... | @@ -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'] | ... | ... |
src/components/MessageDetail.vue
0 → 100644
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 | - | ||
| 571 | - .detail-header { | ||
| 572 | - padding: 32rpx 24rpx; | ||
| 573 | - border-bottom: 1rpx solid #f0f0f0; | ||
| 574 | - background: #ffffff; | ||
| 575 | - flex-shrink: 0; | ||
| 576 | - } | ||
| 577 | 555 | ||
| 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; |
| ... | @@ -675,7 +606,8 @@ onMounted(() => { | ... | @@ -675,7 +606,8 @@ onMounted(() => { |
| 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; |
| ... | @@ -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); | ... | ... |
-
Please register or login to post a comment