upload project
This commit is contained in:
commit
06961cae04
422 changed files with 110626 additions and 0 deletions
3222
pages/message/chat/chat.js
Normal file
3222
pages/message/chat/chat.js
Normal file
File diff suppressed because it is too large
Load diff
13
pages/message/chat/chat.json
Normal file
13
pages/message/chat/chat.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"navigationBarTitleText": "聊天",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#f8f9fa",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50,
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"voice-message": "/components/voice-message/voice-message"
|
||||
}
|
||||
}
|
||||
344
pages/message/chat/chat.wxml
Normal file
344
pages/message/chat/chat.wxml
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
<!--聊天页面-->
|
||||
<page-meta page-style="overflow: hidden;" />
|
||||
|
||||
<view class="chat-container theme-dark {{showEmojiPanel ? 'with-emoji' : ''}}"
|
||||
style="height: {{screenHeight + 'px'}}; bottom: 23px;">
|
||||
<!-- 加载状态指示器 -->
|
||||
<view wx:if="{{isLoading}}" class="loading-container">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">正在加载聊天...</text>
|
||||
</view>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<scroll-view
|
||||
class="message-list"
|
||||
style="bottom: {{inputAreaHeight + keyboardHeight + emojiPanelHeight}}px;"
|
||||
scroll-y="{{true}}"
|
||||
scroll-top="{{scrollTop}}"
|
||||
scroll-into-view="{{scrollIntoView}}"
|
||||
enhanced="{{true}}"
|
||||
show-scrollbar="{{false}}"
|
||||
bindscrolltoupper="loadMoreMessages"
|
||||
bindscroll="onScroll"
|
||||
bindtap="hideInputMethod"
|
||||
upper-threshold="50"
|
||||
>
|
||||
<!-- 🔥 顶部加载更多提示 - 只在有消息且有更多消息时显示 -->
|
||||
<view wx:if="{{hasMore && messages.length > 5}}" class="load-more-top" bindtap="loadMoreMessages">
|
||||
<text class="load-more-text">{{loadingMessages ? '加载中...' : '上拉加载更多消息'}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{messages}}" wx:key="messageId" class="message-item" id="msg-{{item.messageId}}">
|
||||
<!-- 日期分隔线(仅日期) -->
|
||||
<view wx:if="{{item.showDateDivider}}" class="time-divider">
|
||||
<text class="time-text">{{item.dateText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 消息内容:根据是否为自己,交换头像与消息体的渲染顺序以确保两侧显示正确 -->
|
||||
<view class="message-wrapper {{item.isSelf ? 'self' : 'other'}}">
|
||||
<!-- 自己的消息:先渲染消息体,再渲染头像(头像在右侧) -->
|
||||
<block wx:if="{{item.isSelf}}" class="self">
|
||||
<view class="message-body ">
|
||||
<!-- 自己的消息不显示昵称 -->
|
||||
|
||||
<!-- 单行(右侧):气泡内显示时间/状态 -->
|
||||
<view class="message-line self">
|
||||
<view class="message-content {{item.msgType}} {{item.isRecalled ? 'recalled' : ''}}"
|
||||
bindlongpress="showMessageMenu" bindtap="hideInputMethod" data-message="{{item}}">
|
||||
|
||||
<!-- 撤回消息显示 -->
|
||||
<view wx:if="{{item.isRecalled}}" class="recalled-content">
|
||||
<text class="recalled-icon">↩️</text>
|
||||
<text class="recalled-text">{{item.content}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 普通消息显示 -->
|
||||
<text wx:elif="{{item.msgType === 'text'}}" class="text-content">{{item.content}}</text>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'image'}}">
|
||||
<view wx:if="{{item.content.type === 'error'}}" class="error-content">
|
||||
<text class="error-text">❌ 图片加载失败</text>
|
||||
<text class="error-detail">{{item.content.error}}</text>
|
||||
<text class="error-original">原始内容: {{item.content.originalContent}}</text>
|
||||
</view>
|
||||
<image wx:else class="image-content" src="{{item.content.url || item.content}}" mode="widthFix"
|
||||
bindtap="previewImage" data-url="{{item.content.url || item.content}}"
|
||||
data-type="{{item.content.type}}" binderror="onImageError">
|
||||
</image>
|
||||
</view>
|
||||
|
||||
<voice-message wx:elif="{{item.msgType === 'audio' || item.msgType === 'voice'}}"
|
||||
voice-data="{{item.content}}" is-self="{{item.isSelf}}" message-id="{{item.messageId}}">
|
||||
</voice-message>
|
||||
|
||||
<video wx:elif="{{item.msgType === 'video'}}" class="video-content" src="{{item.content}}"
|
||||
poster="{{item.thumbnail}}" objectFit="cover" controls>
|
||||
</video>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'location'}}" class="location-content" bindtap="showLocation"
|
||||
data-message="{{item}}">
|
||||
<image class="location-icon" src="/images/loca.svg" mode="aspectFit"></image>
|
||||
<view class="location-info">
|
||||
<text class="location-name">{{item.locationName || '位置信息'}}</text>
|
||||
<text class="location-address">{{item.locationAddress}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'file'}}" class="file-content" bindtap="downloadFile"
|
||||
data-message="{{item}}">
|
||||
<image class="file-icon" src="/images/download.svg" mode="aspectFit"></image>
|
||||
<view class="file-info">
|
||||
<text class="file-name">{{item.fileName || '文件'}}</text>
|
||||
<text class="file-size">{{item.fileSize || '未知大小'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<image wx:elif="{{item.msgType === 'sticker'}}" class="sticker-content"
|
||||
src="{{item.content.url || item.content}}" mode="aspectFit">
|
||||
</image>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'card'}}" class="card-content" bindtap="viewCard"
|
||||
data-message="{{item}}">
|
||||
<text class="card-icon">👤</text>
|
||||
<view class="card-info">
|
||||
<text class="card-name">{{item.cardName || '联系人'}}</text>
|
||||
<text class="card-phone">{{item.cardPhone || ''}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'system'}}" class="system-content">
|
||||
<text class="system-text">{{item.content}}</text>
|
||||
</view>
|
||||
|
||||
<text wx:else class="unknown-content">[不支持的消息类型]</text>
|
||||
|
||||
<!-- 气泡内时间与状态(撤回消息不显示) -->
|
||||
<!-- deliveryStatus 使用 NIM SDK V2 枚举值: 0=未知, 1=成功, 2=失败, 3=发送中 -->
|
||||
<view wx:if="{{item.msgType !== 'system' && !item.isRecalled}}" class="bubble-meta">
|
||||
<text class="bubble-time">{{item.bubbleTime}}</text>
|
||||
<text wx:if="{{item.deliveryStatus === 1}}" class="meta-status meta-delivered">✓</text>
|
||||
<text wx:elif="{{item.deliveryStatus === 3}}" class="meta-status meta-sending">●</text>
|
||||
<text wx:elif="{{item.deliveryStatus === 2}}" class="meta-status meta-failed">!</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发送失败的"重发"操作(仅自己消息显示,出现在气泡下方靠右) -->
|
||||
<!-- deliveryStatus === 2 表示失败 (V2NIM_MESSAGE_SENDING_STATE_FAILED) -->
|
||||
<view wx:if="{{item.isSelf && item.deliveryStatus === 2 && !item.isRecalled && item.msgType !== 'system'}}" class="resend-row">
|
||||
<view class="resend-action" bindtap="resendMessage" data-message="{{item}}">
|
||||
<text class="resend-icon">↻</text>
|
||||
<text class="resend-text">重新发送</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自己的头像(右侧) -->
|
||||
<view class="avatar" bindtap="onSelfAvatarTap" data-user-id="{{userInfo.user.customId}}">
|
||||
<view wx:if="{{userAvatar && userAvatar.length > 0}}" class="avatar-image">
|
||||
<image src="{{userAvatar}}" mode="aspectFill" bindload="onAvatarLoad" binderror="onAvatarError" />
|
||||
</view>
|
||||
<view wx:else class="avatar-placeholder">
|
||||
<text>👤</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 对方的消息:头像在左,消息体在右 -->
|
||||
<block wx:else class="other">
|
||||
<view class="avatar" bindtap="onPeerAvatarTap" data-sender-id="{{item.senderId || item.senderCustomId}}">
|
||||
<view wx:if="{{item.senderAvatar}}" class="avatar-image">
|
||||
<image src="{{item.senderAvatar}}" mode="aspectFill" bindload="onAvatarLoad" binderror="onAvatarError" />
|
||||
</view>
|
||||
<view wx:else class="avatar-placeholder">
|
||||
<text>👥</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="message-body">
|
||||
<view class="sender-name" wx:if="{{!item.isSelf && chatType === 1}}">
|
||||
{{item.senderName}}
|
||||
</view>
|
||||
|
||||
<!-- 单行(左侧):气泡内显示时间(仅时间) -->
|
||||
<view class="message-line other">
|
||||
<view class="message-content {{item.msgType}} {{item.isRecalled ? 'recalled' : ''}}"
|
||||
bindlongpress="showMessageMenu" bindtap="hideInputMethod" data-message="{{item}}">
|
||||
<!-- 撤回消息显示 -->
|
||||
<view wx:if="{{item.isRecalled}}" class="recalled-content">
|
||||
<text class="recalled-icon">↩️</text>
|
||||
<text class="recalled-text">{{item.content}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 普通消息显示 -->
|
||||
<text wx:elif="{{item.msgType === 'text'}}" class="text-content">{{item.content}}</text>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'image'}}">
|
||||
<view wx:if="{{item.content.type === 'error'}}" class="error-content">
|
||||
<text class="error-text">❌ 图片加载失败</text>
|
||||
<text class="error-detail">{{item.content.error}}</text>
|
||||
<text class="error-original">原始内容: {{item.content.originalContent}}</text>
|
||||
</view>
|
||||
<image wx:else class="image-content" src="{{item.content.url || item.content}}" mode="widthFix"
|
||||
bindtap="previewImage" data-url="{{item.content.url || item.content}}"
|
||||
data-type="{{item.content.type}}" binderror="onImageError">
|
||||
</image>
|
||||
</view>
|
||||
|
||||
<voice-message wx:elif="{{item.msgType === 'audio' || item.msgType === 'voice'}}"
|
||||
voice-data="{{item.content}}" is-self="{{item.isSelf}}" message-id="{{item.messageId}}">
|
||||
</voice-message>
|
||||
|
||||
<video wx:elif="{{item.msgType === 'video'}}" class="video-content" src="{{item.content}}"
|
||||
poster="{{item.thumbnail}}" controls>
|
||||
</video>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'location'}}" class="location-content" bindtap="showLocation"
|
||||
data-message="{{item}}">
|
||||
<image class="location-icon" src="/images/loca.svg" mode="aspectFit"></image>
|
||||
<view class="location-info">
|
||||
<text class="location-name">{{item.locationName || '位置信息'}}</text>
|
||||
<text class="location-address">{{item.locationAddress}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'file'}}" class="file-content" bindtap="downloadFile"
|
||||
data-message="{{item}}">
|
||||
<image class="file-icon" src="/images/download.svg" mode="aspectFit"></image>
|
||||
<view class="file-info">
|
||||
<text class="file-name">{{item.fileName || '文件'}}</text>
|
||||
<text class="file-size">{{item.fileSize || '未知大小'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<image wx:elif="{{item.msgType === 'sticker'}}" class="sticker-content"
|
||||
src="{{item.content.url || item.content}}" mode="aspectFit">
|
||||
</image>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'card'}}" class="card-content" bindtap="viewCard"
|
||||
data-message="{{item}}">
|
||||
<text class="card-icon">👤</text>
|
||||
<view class="card-info">
|
||||
<text class="card-name">{{item.cardName || '联系人'}}</text>
|
||||
<text class="card-phone">{{item.cardPhone || ''}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{item.msgType === 'system'}}" class="system-content">
|
||||
<text class="system-text">{{item.content}}</text>
|
||||
</view>
|
||||
|
||||
<text wx:else class="unknown-content">[不支持的消息类型]</text>
|
||||
|
||||
<!-- 气泡内时间(对方仅时间,撤回消息不显示) -->
|
||||
<view wx:if="{{item.msgType !== 'system' && !item.isRecalled}}" class="bubble-meta">
|
||||
<text class="bubble-time">{{item.bubbleTime}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 回到最新消息按钮 -->
|
||||
<view class="scroll-to-bottom-btn {{showScrollToBottom ? 'show' : ''}}" bindtap="scrollToBottom"
|
||||
wx:if="{{showScrollToBottom}}">
|
||||
<view class="btn-icon">↓</view>
|
||||
</view>
|
||||
|
||||
<!-- 输入框区域 -->
|
||||
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: {{safeAreaBottom}}px;">
|
||||
<view class="input-row">
|
||||
<!-- 语音按钮 -->
|
||||
<view class="tool-btn" bindtap="toggleInputType">
|
||||
<image class="{{inputType === 'text' ? 'icon-mic' : 'icon-keyboard'}}"
|
||||
src="{{inputType === 'text' ? '/images/emoji/s-input.svg' : '/images/emoji/f-message.svg'}}"
|
||||
mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<!-- 文本输入 -->
|
||||
<view wx:if="{{inputType === 'text'}}" class="text-input-wrapper">
|
||||
<textarea class="text-input" placeholder="请输入消息..." value="{{inputText}}" bindinput="onInputChange"
|
||||
bindconfirm="sendTextMessage" bindcompositionstart="onCompositionStart" bindcompositionend="onCompositionEnd"
|
||||
bindlinechange="onLineChange" bindkeyboardheightchange="onKeyboardHeightChange"
|
||||
focus="{{inputFocus}}" maxlength="500" confirm-type="send"
|
||||
adjust-position="{{false}}" hold-keyboard="{{true}}" auto-height="{{true}}" show-confirm-bar="{{false}}"
|
||||
fixed="{{false}}" cursor-spacing="24" disable-default-padding="{{true}}" />
|
||||
</view>
|
||||
|
||||
<!-- 语音录制(微信风格:按住说话,上滑取消) -->
|
||||
<view wx:else class="voice-input-wrapper">
|
||||
<button class="voice-btn {{recording ? 'recording' : ''}}" bindtouchstart="onVoiceTouchStart"
|
||||
bindtouchmove="onVoiceTouchMove" bindtouchend="onVoiceTouchEnd" bindtouchcancel="onVoiceTouchCancel">
|
||||
<image class="inline-icon" src="/images/emoji/s-input.svg" mode="aspectFit" />
|
||||
按住 说话
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 表情按钮 -->
|
||||
<view class="tool-btn" bindtap="toggleEmojiPanel">
|
||||
<image class="icon-emoji" src="/images/emoji/m-emoji.svg" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<!-- 更多按钮 -->
|
||||
<view wx:if="{{inputText.length === 0}}" class="tool-btn" bindtap="toggleMorePanel">
|
||||
<image class="icon-more" src="/images/emoji/add-circle.svg" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<!-- 发送按钮 -->
|
||||
<view wx:if="{{inputText.length > 0}}" class="send-btn" bindtap="sendTextMessage">
|
||||
<text>发送</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表情面板 -->
|
||||
<view wx:if="{{showEmojiPanel}}" class="emoji-panel">
|
||||
<scroll-view class="emoji-scroll" scroll-y="{{true}}" show-scrollbar="{{false}}">
|
||||
<view class="emoji-list">
|
||||
<text wx:for="{{commonEmojis}}" wx:key="*this" class="emoji-item" bindtap="selectEmoji"
|
||||
data-emoji="{{item}}">{{item}}</text>
|
||||
</view>
|
||||
<!-- 底部安全区占位,避免最后一排被遮挡,保证能继续滚动到最底部 -->
|
||||
<view class="emoji-bottom-spacer" style="height: {{safeAreaBottom}}px;"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 更多功能面板:移到 input-area 外,避免被其 overflow 裁切 -->
|
||||
<view wx:if="{{showMorePanel}}" class="more-panel-overlay" bindtap="toggleMorePanel">
|
||||
<view class="more-panel" catchtap="stopPropagation">
|
||||
<view class="more-panel-header">
|
||||
<text class="more-panel-title">选择功能</text>
|
||||
<view class="more-panel-close" bindtap="toggleMorePanel">✕</view>
|
||||
</view>
|
||||
<view class="more-list">
|
||||
<view class="more-item" bindtap="showMediaPicker">
|
||||
<image class="more-icon" src="/images/cam.svg" mode="aspectFit"></image>
|
||||
<text class="more-text">拍照/录像</text>
|
||||
</view>
|
||||
<view class="more-item" bindtap="chooseImageFromAlbum">
|
||||
<image class="more-icon" src="/images/Album.svg" mode="aspectFit"></image>
|
||||
<text class="more-text">相册</text>
|
||||
</view>
|
||||
<view class="more-item" bindtap="selectFile">
|
||||
<image class="more-icon" src="/images/download.svg" mode="aspectFit"></image>
|
||||
<text class="more-text">文件</text>
|
||||
</view>
|
||||
<view class="more-item" bindtap="selectLocation">
|
||||
<image class="more-icon" src="/images/loca.svg" mode="aspectFit"></image>
|
||||
<text class="more-text">位置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 录音提示 -->
|
||||
<view wx:if="{{recording}}" class="recording-tips">
|
||||
<text wx:if="{{!voiceCancel}}">松开 结束, 上滑 取消</text>
|
||||
<text wx:else>松开手指,取消发送</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
899
pages/message/chat/chat.wxss
Normal file
899
pages/message/chat/chat.wxss
Normal file
|
|
@ -0,0 +1,899 @@
|
|||
/* 🎨 现代化聊天页面样式 - 深色主题 2025 */
|
||||
|
||||
/* 🎨 CSS变量定义 - 黑色主题(本文件局部覆盖) */
|
||||
page {
|
||||
--primary-color: #0A84FF; /* accent */
|
||||
--primary-light: #3EA8FF;
|
||||
--primary-dark: #0056CC;
|
||||
--background-color: #070709; /* 页面背景主色(几乎黑) */
|
||||
--surface-color: #0F0F11; /* 气泡/卡片背景 */
|
||||
--text-primary: #ECECEC; /* 主要文字-浅色 */
|
||||
--text-secondary: #A8A8A8; /* 次要文字 */
|
||||
--text-tertiary: #7A7A7A;
|
||||
--border-color: rgba(255,255,255,0.06);
|
||||
--shadow-light: 0 1rpx 6rpx rgba(0, 0, 0, 0.6);
|
||||
--shadow-medium: 0 6rpx 18rpx rgba(0, 0, 0, 0.7);
|
||||
--radius-small: 8rpx;
|
||||
--radius-medium: 12rpx;
|
||||
--radius-large: 20rpx;
|
||||
/* 确保page占满可用高度 */
|
||||
height: 100%;
|
||||
background-color: var(--background-color);
|
||||
|
||||
}
|
||||
|
||||
/* 固定深色主题容器样式 */
|
||||
.chat-container.theme-dark {
|
||||
box-sizing: border-box;
|
||||
/* border: 10px solid red; */
|
||||
background: linear-gradient(130deg, #111623,#054B58);
|
||||
color: var(--text-primary);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 简化:去除主题切换过渡 */
|
||||
.chat-container { transition: background 0s, color 0s; }
|
||||
|
||||
/* 消息气泡、输入区等关键区域同步过渡,避免突变 */
|
||||
.message-content,
|
||||
.input-area,
|
||||
.emoji-panel,
|
||||
.more-panel,
|
||||
.time-text,
|
||||
.tool-btn,
|
||||
.message-wrapper.self .message-content,
|
||||
.message-wrapper.other .message-content {
|
||||
transition: background-color 280ms ease, color 280ms ease, border-color 280ms ease, box-shadow 280ms ease;
|
||||
}
|
||||
|
||||
/* 现代化导航栏(chat.wxml 未使用,相关样式移除;保留通用图标尺寸变量) */
|
||||
.icon-search, .icon-more {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-medium);
|
||||
transition: all 0.3s ease;
|
||||
/* when used as <image>, size via width/height; when used as <text>, below font props are ignored */
|
||||
font-size: 32rpx;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.icon-search:active, .icon-more:active {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.message-item {
|
||||
margin-bottom: 32rpx;
|
||||
padding: 0 32rpx;
|
||||
animation: messageSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes messageSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 现代化时间分隔线 */
|
||||
.time-divider {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 40rpx 0 32rpx 0;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
background: rgba(255,255,255,0.03);
|
||||
color: var(--text-secondary);
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 999rpx;
|
||||
letter-spacing: 0.5rpx;
|
||||
box-shadow: none;
|
||||
backdrop-filter: blur(6rpx);
|
||||
}
|
||||
|
||||
/* 消息容器 - 布局 */
|
||||
.message-wrapper {
|
||||
display: flex;
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
/* border: 1px solid red; */
|
||||
}
|
||||
|
||||
/* 对方消息:头像在左 */
|
||||
.message-wrapper.other {
|
||||
flex-direction: row !important;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* 我的消息:头像在右 */
|
||||
.message-wrapper.self {
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 头像样式 */
|
||||
.avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message-wrapper.other .avatar {
|
||||
margin: 0 32rpx 0 0;
|
||||
}
|
||||
|
||||
.message-wrapper.self .avatar {
|
||||
margin: 0 0 0 20rpx;
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-image image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg,#1B1B1D 0%, #101012 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.message-wrapper.self .avatar-placeholder {
|
||||
background: linear-gradient(135deg,#0A274E 0%, #05203A 100%);
|
||||
color: #FFFFFF;
|
||||
font-weight: 700;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
/* 消息主体 */
|
||||
.message-body {
|
||||
/* flex: 1; */
|
||||
/* height: 200rpx; */
|
||||
max-width: 500rpx;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
/* border: 1px solid red; */
|
||||
}
|
||||
.message-line{
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex-shrink: 0;
|
||||
/* height: 90rpx; */
|
||||
/* border: 1px solid rgb(12, 235, 98); */
|
||||
}
|
||||
|
||||
.message-wrapper.self .message-line {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.message-wrapper.other .message-line {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
/* 图片消息的 message-line 需要允许内容自适应 */
|
||||
.message-line .message-content.image {
|
||||
width: auto;
|
||||
max-width: 420rpx;
|
||||
min-width: 200rpx;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message-line .message-content.video {
|
||||
width: auto;
|
||||
max-width: 400rpx;
|
||||
min-width: 160rpx;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* 图片消息外层容器 - 确保有正确的布局 */
|
||||
.message-content.image > view {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.message-wrapper.other .message-body {
|
||||
align-items: flex-start;
|
||||
margin-left: 0rpx;
|
||||
}
|
||||
|
||||
.message-wrapper.self .message-body {
|
||||
align-items: flex-end;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
/* 发送者名称(群聊) */
|
||||
.sender-name {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
/* 消息气泡 */
|
||||
.message-content {
|
||||
border-radius: var(--radius-large);
|
||||
padding: 20rpx 28rpx 36rpx 28rpx; /* 为气泡内时间预留底部空间 */
|
||||
position: relative;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
/* 让气泡宽度跟随容器铺满(在 message-body 容器内) */
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* line-height: 1.6; */
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
box-shadow: none;
|
||||
backdrop-filter: blur(6rpx);
|
||||
transition: all 0.15s ease;
|
||||
color: black;
|
||||
|
||||
}
|
||||
.self .message-content:not(.image):not(.video)::after{
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 100%;
|
||||
margin-left: -10rpx;
|
||||
border: 25rpx solid transparent;
|
||||
border-left: 15px solid #4DD1A1;
|
||||
}
|
||||
.other .message-content:not(.image):not(.video)::before{
|
||||
content: "";
|
||||
top: 20rpx;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
margin-right: -10rpx;
|
||||
border: 25rpx solid transparent;
|
||||
border-right: 15px solid #D9D9D9;
|
||||
}
|
||||
|
||||
/* 对方消息气泡(深色) */
|
||||
.message-wrapper.other .message-content {
|
||||
background: #D9D9D9;
|
||||
/* color: var(--text-primary); */
|
||||
border: 1rpx solid rgba(255,255,255,0.03);
|
||||
border-top-left-radius: var(--radius-small);
|
||||
}
|
||||
|
||||
.message-wrapper.other .message-content:not(.image):not(.video):hover {
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
/* 对方的图片消息:无气泡 */
|
||||
.message-wrapper.other .message-content.image {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
max-width: 420rpx;
|
||||
margin: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message-wrapper.other .message-content.image:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 对方的视频消息:无气泡 */
|
||||
.message-wrapper.other .message-content.video {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
backdrop-filter: none;
|
||||
width: auto;
|
||||
max-width: 420rpx;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message-wrapper.other .message-content.video:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 我的消息气泡(偏蓝) */
|
||||
.message-wrapper.self .message-content {
|
||||
/* background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%); */
|
||||
background-color: #4DD1A1;
|
||||
/* color: #FFFFFF; */
|
||||
border-top-right-radius: var(--radius-small);
|
||||
box-shadow: 0 6rpx 20rpx rgba(10,132,255,0.18);
|
||||
}
|
||||
|
||||
.message-wrapper.self .message-content:not(.image):not(.video):hover {
|
||||
box-shadow: 0 8rpx 28rpx rgba(10,132,255,0.14);
|
||||
}
|
||||
|
||||
/* 我的图片消息:无气泡 */
|
||||
.message-wrapper.self .message-content.image {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border-top-right-radius: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
backdrop-filter: none;
|
||||
width: auto;
|
||||
max-width: 420rpx;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.message-wrapper.self .message-content.image:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 我的视频消息:无气泡 */
|
||||
.message-wrapper.self .message-content.video {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border-top-right-radius: 0;
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
backdrop-filter: none;
|
||||
width: auto;
|
||||
max-width: 420rpx;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.message-wrapper.self .message-content.video:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 为气泡内时间/状态预留右侧空间(收窄,避免出现一列明显空白) */
|
||||
.message-wrapper.self .message-content:not(.image):not(.video) { padding-right: 70rpx; }
|
||||
.message-wrapper.other .message-content:not(.image):not(.video) { padding-right: 70rpx; }
|
||||
|
||||
/* 语音消息在气泡中的宽度约束,确保不会超出屏幕 */
|
||||
.message-content.voice, .message-content.audio {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.message-content.voice .voice-message-container,
|
||||
.message-content.audio .voice-message-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 气泡内时间与状态(右下角) */
|
||||
.bubble-meta {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
bottom: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.bubble-meta { white-space: nowrap; }
|
||||
|
||||
/* 图片和视频消息不显示时间/状态 */
|
||||
.message-content.image .bubble-meta,
|
||||
.message-content.video .bubble-meta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bubble-time {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255,255,255,0.75);
|
||||
}
|
||||
.message-wrapper.other .bubble-time { color: var(--text-secondary); }
|
||||
|
||||
.meta-status { font-size: 22rpx; line-height: 1; }
|
||||
.meta-read { color: #B2E5FF; }
|
||||
.meta-delivered { color: rgba(255,255,255,0.9); }
|
||||
.meta-sending { color: var(--text-tertiary); animation: pulse 1.5s ease-in-out infinite; }
|
||||
.meta-failed { color: #ff6b6b; font-weight: 700; }
|
||||
|
||||
/* 失败重发行:显示在自己消息气泡下方靠右 */
|
||||
.resend-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.resend-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
padding: 8rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
color: #ff6b6b;
|
||||
background: rgba(255,107,107,0.1);
|
||||
border: 1rpx solid rgba(255,107,107,0.2);
|
||||
}
|
||||
.resend-action:active { transform: scale(0.98); background: rgba(255,107,107,0.14); }
|
||||
.resend-icon { font-size: 22rpx; font-weight: 700; }
|
||||
.resend-text { font-size: 24rpx; color: #ff7b7b; }
|
||||
|
||||
|
||||
/* 文本、图片等样式保持,但颜色适配深色 */
|
||||
.text-content {
|
||||
font-size: 32rpx;
|
||||
line-height: 1.6;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5rpx;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* 撤回消息样式 */
|
||||
.recalled-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background: transparent;
|
||||
border: 1rpx dashed var(--border-color);
|
||||
border-radius: var(--radius-medium);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 上传进度条样式 */
|
||||
.upload-progress {
|
||||
margin-top: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.upload-progress-bar {
|
||||
width: 240rpx;
|
||||
height: 8rpx;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.upload-progress-fill {
|
||||
height: 100%;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
.upload-progress-text {
|
||||
font-size: 22rpx;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.recalled-icon {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.recalled-text {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.message-content.recalled {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
min-width: 240rpx;
|
||||
}
|
||||
|
||||
.theme-light .recalled-content {
|
||||
color: var(--text-secondary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.image-content {
|
||||
width: 420rpx;
|
||||
max-width: 420rpx;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.image-content:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.error-content {
|
||||
padding: 20rpx;
|
||||
background: rgba(255, 60, 60, 0.08);
|
||||
border: 1px solid rgba(255,60,60,0.12);
|
||||
border-radius: 12rpx;
|
||||
max-width: 400rpx;
|
||||
}
|
||||
|
||||
.error-text { color: #ff6b6b; }
|
||||
.error-detail { color: #ffb86b; }
|
||||
.error-original { color: var(--text-secondary); }
|
||||
|
||||
/* 文件消息 */
|
||||
.file-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 240rpx;
|
||||
padding: 20rpx;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border-radius: var(--radius-medium);
|
||||
backdrop-filter: blur(6rpx);
|
||||
border: 1rpx solid rgba(255,255,255,0.03);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.file-content:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
/* 卡片、贴纸等 */
|
||||
.card-content { background: rgba(255,255,255,0.02); }
|
||||
.card-name, .file-name { color: var(--text-primary); }
|
||||
.card-phone, .file-size { color: var(--text-secondary); }
|
||||
|
||||
.system-content { background: rgba(255,255,255,0.02); color: var(--text-secondary); }
|
||||
|
||||
.video-content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 420rpx;
|
||||
max-height: 820rpx;
|
||||
min-width: 160rpx;
|
||||
min-height: 360rpx;
|
||||
border-radius: 16rpx;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
/* 音频、视频、位置等适配 */
|
||||
.location-content { background: rgba(255,255,255,0.02); border: 1rpx solid rgba(255,255,255,0.03); }
|
||||
/* icon images inside message content */
|
||||
.location-icon, .file-icon { width: 36rpx; height: 36rpx; margin-right: 12rpx; display: inline-block; }
|
||||
|
||||
@keyframes pulse { 0%,100%{transform:scale(1)}50%{transform:scale(1.05)} }
|
||||
|
||||
.load-more-top {
|
||||
text-align: center;
|
||||
padding: 18rpx 28rpx;
|
||||
background: rgba(255,255,255,0.02);
|
||||
margin-bottom: 18rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 16rpx 18rpx 16rpx;
|
||||
}
|
||||
|
||||
.load-more-text { color: var(--text-secondary); font-size: 26rpx; }
|
||||
|
||||
/* 输入区域 - 使用 fixed 定位固定在底部 */
|
||||
.input-area {
|
||||
background: linear-gradient(180deg, rgba(12,12,14,0.98) 0%, rgba(10,10,12,0.98) 100%);
|
||||
border-top: 1rpx solid rgba(255,255,255,0.03);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
transition: bottom 0.3s ease;
|
||||
backdrop-filter: blur(10rpx);
|
||||
box-shadow: 0 -8rpx 30rpx rgba(0, 0, 0, 0.7);
|
||||
/* max-height: 30vh; */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 16rpx 24rpx;
|
||||
min-height: 80rpx;
|
||||
max-height: 200rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tool-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
min-height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border-radius: var(--radius-medium);
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
border: 1rpx solid rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.tool-btn:active { transform: scale(0.95); background: rgba(255,255,255,0.02); }
|
||||
|
||||
.text-input-wrapper {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
min-width: 0; /* 确保能够收缩 */
|
||||
max-width: calc(100% - 160rpx); /* 为左右按钮预留空间 */
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1rpx solid rgba(255,255,255,0.03);
|
||||
border-radius: var(--radius-large);
|
||||
padding: 16rpx 24rpx;
|
||||
font-size: 32rpx;
|
||||
min-height: 48rpx;
|
||||
max-height: 120rpx;
|
||||
width: 100%;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.18s ease;
|
||||
resize: none;
|
||||
line-height: 1.4;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.text-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 6rpx rgba(10,132,255,0.06); }
|
||||
|
||||
/* 语音输入容器与文本输入保持同样的伸展规则 */
|
||||
.voice-input-wrapper {
|
||||
flex: 1;
|
||||
margin-right: 16rpx;
|
||||
min-width: 0; /* 允许收缩,避免被按钮挤出 */
|
||||
max-width: calc(100% - 160rpx); /* 为左右按钮预留空间,和 text-input-wrapper 保持一致 */
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.voice-btn {
|
||||
background: rgba(255,255,255,0.02);
|
||||
color: var(--text-primary);
|
||||
flex-shrink: 0;
|
||||
min-height: 48rpx;
|
||||
border-radius: var(--radius-large);
|
||||
border: 1rpx solid rgba(255,255,255,0.03);
|
||||
font-size: 28rpx;
|
||||
align-self: flex-end;
|
||||
width: 100%; /* 占满容器宽度 */
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx 24rpx; /* 与文本输入视觉统一 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.voice-btn.recording { background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; }
|
||||
|
||||
.send-btn {
|
||||
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
|
||||
color: white;
|
||||
padding: 16rpx 28rpx;
|
||||
border-radius: var(--radius-large);
|
||||
border: none;
|
||||
font-size: 28rpx;
|
||||
align-self: flex-end;
|
||||
flex-shrink: 0;
|
||||
min-height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 表情面板 */
|
||||
.emoji-panel {
|
||||
background: linear-gradient(180deg, rgba(16,16,18,0.98) 0%, rgba(12,12,14,0.98) 100%);
|
||||
border-top: 1rpx solid rgba(255,255,255,0.03);
|
||||
padding: 24rpx 20rpx;
|
||||
max-height: 400rpx;
|
||||
backdrop-filter: blur(12rpx);
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.emoji-scroll {
|
||||
height: 100%;
|
||||
max-height: 352rpx; /* 400rpx - 48rpx padding */
|
||||
}
|
||||
|
||||
.emoji-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 16rpx;
|
||||
padding: 8rpx 0 calc(env(safe-area-inset-bottom) + 20rpx) 0; /* 为底部安全区留白,避免被遮挡 */
|
||||
}
|
||||
|
||||
.emoji-item {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 52rpx;
|
||||
border-radius: var(--radius-medium);
|
||||
background: rgba(255,255,255,0.02);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.emoji-item:active {
|
||||
background: rgba(255,255,255,0.08);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 表情滚动底部占位,提供安全区滚动空间 */
|
||||
.emoji-bottom-spacer { height: env(safe-area-inset-bottom); }
|
||||
|
||||
/* 更多面板 */
|
||||
.more-panel-overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: flex-end; /* panel at bottom */
|
||||
justify-content: center;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 2300; /* above input-area (1000) and other floating widgets */
|
||||
}
|
||||
|
||||
.more-panel {
|
||||
background: var(--surface-color, #0B0B0D);
|
||||
color: var(--text-primary);
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 12rpx 0 24rpx 0;
|
||||
width: 100%;
|
||||
max-height: 60vh;
|
||||
box-shadow: 0 -8rpx 32rpx rgba(0,0,0,0.6);
|
||||
z-index: 2400; /* ensure panel sits above overlay and any page floats */
|
||||
}
|
||||
|
||||
/* theme-light overrides for panel */
|
||||
.theme-light .more-panel-overlay { background: rgba(0,0,0,0.35); }
|
||||
.theme-light .more-panel { background: linear-gradient(180deg, var(--background-color) 0%, #F8F8F8 100%); color: var(--text-primary); box-shadow: 0 -8rpx 32rpx rgba(0,0,0,0.08); }
|
||||
.more-panel-header { display:flex; justify-content:space-between; align-items:center; padding:24rpx 28rpx; border-bottom:1rpx solid rgba(255,255,255,0.03); }
|
||||
.more-panel-title { font-size:36rpx; font-weight:600; color:var(--text-primary); }
|
||||
.more-panel-header { position: relative; }
|
||||
.more-panel-close { width:56rpx; height:56rpx; position:absolute; right:16rpx; top:12rpx; display:flex; align-items:center; justify-content:center; background: rgba(0,0,0,0.12); border-radius:28rpx; font-size:28rpx; color:var(--text-secondary); }
|
||||
.more-panel-close:active { background: rgba(0,0,0,0.16); transform:scale(0.96); }
|
||||
|
||||
/* more panel content layout */
|
||||
.more-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx 24rpx;
|
||||
padding: 8rpx 20rpx 12rpx 20rpx;
|
||||
justify-content: space-around; /* center items evenly */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.more-item {
|
||||
min-width: 84rpx;
|
||||
width: 22%;
|
||||
max-width: 120rpx;
|
||||
height: 96rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border-radius: 16rpx;
|
||||
box-shadow: var(--shadow-light);
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
padding: 6rpx;
|
||||
}
|
||||
|
||||
.more-item:active { transform: scale(0.96); background: rgba(255,255,255,0.03); }
|
||||
|
||||
.more-icon { width: 64rpx; height: 64rpx; margin-bottom: 6rpx; display: block; }
|
||||
.more-text { font-size: 22rpx; color: var(--text-secondary); line-height: 1.1; }
|
||||
|
||||
/* ensure panel respects safe-area at bottom */
|
||||
.more-panel { padding-bottom: calc(env(safe-area-inset-bottom) + 16rpx); }
|
||||
|
||||
/* 录音提示 */
|
||||
.recording-tips { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background: rgba(0,0,0,0.85); color:var(--text-primary); padding:28rpx 36rpx; border-radius:12rpx; font-size:30rpx; z-index:2000; }
|
||||
|
||||
/* 加载 */
|
||||
.loading-container { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; justify-content:center; z-index:2000; background: rgba(0,0,0,0.75); border-radius:16rpx; padding:28rpx; box-shadow:0 12rpx 36rpx rgba(0,0,0,0.7); backdrop-filter: blur(12rpx); }
|
||||
.loading-spinner { width:56rpx; height:56rpx; border:4rpx solid rgba(255,255,255,0.08); border-top:4rpx solid var(--primary-color); border-radius:50%; animation:spin 1s linear infinite; margin-bottom:12rpx; }
|
||||
.loading-text { font-size:26rpx; color:var(--text-secondary); font-weight:500; }
|
||||
|
||||
@keyframes spin { 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} }
|
||||
|
||||
/* 滚动到底部按钮 */
|
||||
|
||||
/* 移除主题切换遮罩动画相关样式 */
|
||||
.scroll-to-bottom-btn { position:fixed; right:32rpx; bottom:240rpx; width:88rpx; height:88rpx; background: rgba(255,255,255,0.06); border-radius:50%; display:flex; align-items:center; justify-content:center; z-index:1000; opacity:0; transform:translateY(20rpx) scale(0.8); transition:all 0.3s cubic-bezier(0.4,0,0.2,1); backdrop-filter: blur(10rpx); box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.6); }
|
||||
.scroll-to-bottom-btn.show { opacity:1; transform:translateY(0) scale(1); }
|
||||
.scroll-to-bottom-btn:active { transform: translateY(0) scale(0.95); background: rgba(255,255,255,0.08); }
|
||||
.btn-icon { color: var(--text-primary); font-size:36rpx; font-weight:bold; text-shadow: 0 1rpx 2rpx rgba(0,0,0,0.6); }
|
||||
|
||||
/* 其他小节样式保持原逻辑,颜色使用变量或深色背景 */
|
||||
|
||||
/* ===== 兼容性修正:避免底部白色撕裂条 ===== */
|
||||
/* 将容器改为 column flex,使 scroll-view 能正确填满剩余高度(避免 calc(100vh - px) 的子像素或安全区差异) */
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%; /* 改为 100% 以适应页面内容区域,而非整个视口 */
|
||||
position: relative; /* make absolute children position relative to this container */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* message-list 使用 fixed 定位固定在视口,不受键盘影响 */
|
||||
.message-list {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 100px; /* 默认值,会被动态 style 覆盖 */
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0 0 0 0; /* 增加底部内边距 */
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
transition: bottom 0.3s ease; /* 平滑过渡 */
|
||||
}
|
||||
|
||||
/* 输入区按钮图标基类(与 wxml 中动态类匹配) */
|
||||
.icon-emoji, .icon-mic, .icon-keyboard {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ensure the 'more' tool icon matches size */
|
||||
.tool-btn .icon-more { width: 48rpx; height: 48rpx; display: block; }
|
||||
|
||||
/* inline icon inside voice button label */
|
||||
.inline-icon { width: 32rpx; height: 32rpx; margin-right: 8rpx; vertical-align: middle; display: inline-block; }
|
||||
1290
pages/message/message.js
Normal file
1290
pages/message/message.js
Normal file
File diff suppressed because it is too large
Load diff
7
pages/message/message.json
Normal file
7
pages/message/message.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"navigationBarTitleText": "聊天",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "default",
|
||||
"backgroundColor": "#f8f9fa"
|
||||
}
|
||||
112
pages/message/message.wxml
Normal file
112
pages/message/message.wxml
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<!--消息页面 - Telegram风格设计-->
|
||||
<view class="telegram-container theme-dark">
|
||||
<view class="telegram-header">
|
||||
<view class="Search-header-left">
|
||||
<input class="Search-input"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value="{{searchKeyword}}"
|
||||
bindinput="onSearchInput"
|
||||
bindconfirm="onSearchInput" />
|
||||
<image class="Search-img" src="../../images/group/Search.svg" mode="" />
|
||||
<!-- 清除按钮 -->
|
||||
<view wx:if="{{searchKeyword}}" class="Search-clear" bindtap="clearSearch">
|
||||
<text>✕</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="telegram-header-right">
|
||||
<image class="Search-img-add" src="../../images/group/add.svg" mode="" bindtap="startNewChat" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 群聊 -->
|
||||
<view class="group-chat">
|
||||
<view class="group-chat-conten">
|
||||
<view class="chat-left">
|
||||
<image class="chat-img" src="../../images/group/groupChat.svg" mode="" />
|
||||
</view>
|
||||
<view class="chat-text">
|
||||
<view class="chat-titel">
|
||||
<view class="titel">群聊</view>
|
||||
<text>你已保存0个群组</text>
|
||||
</view>
|
||||
<view class="chat-rigth">
|
||||
<image class="chat-img-rigth" src="../../images/emoji/r-Return.svg" mode="" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏已取消,点击搜索按钮直接跳转全局搜索页面 -->
|
||||
|
||||
<!-- Telegram风格内容:始终展示会话列表(filteredConversations会随搜索实时过滤) -->
|
||||
<scroll-view class="telegram-content" scroll-y="true"
|
||||
refresher-enabled="true"
|
||||
refresher-triggered="{{refreshing}}"
|
||||
bindrefresherrefresh="onRefresh">
|
||||
<view class="conversations-section">
|
||||
<view class="conversation-item {{item.isPinned ? 'pinned' : ''}} {{item.isMuted ? 'muted' : ''}}"
|
||||
wx:for="{{filteredConversations}}"
|
||||
wx:key="conversationId"
|
||||
bindtap="openChat"
|
||||
bindlongtap="showConversationOptions"
|
||||
data-conversation="{{item}}">
|
||||
|
||||
<!-- 头像 -->
|
||||
<view class="conversation-avatar">
|
||||
<image wx:if="{{item.avatar}}"
|
||||
src="{{item.avatar}}"
|
||||
class="avatar-image"
|
||||
mode="aspectFill" />
|
||||
<view wx:else class="avatar-placeholder">
|
||||
<text class="avatar-text">{{item.name.charAt(0)}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 在线状态角标(仅单聊显示) -->
|
||||
<view wx:if="{{item.type === 'single' || item.chatType === 0}}"
|
||||
class="presence-indicator {{item.isOnline ? 'online' : 'offline'}}"></view>
|
||||
|
||||
<!-- 未读消息徽章 -->
|
||||
<view class="unread-badge" wx:if="{{item.unreadCount > 0}}">
|
||||
<text class="unread-count">{{item.unreadCount > 99 ? '99+' : item.unreadCount}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 置顶标识 -->
|
||||
<view class="pin-indicator" wx:if="{{item.isPinned}}">
|
||||
<text class="pin-icon">📌</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会话信息 -->
|
||||
<view class="conversation-info">
|
||||
<view class="conversation-header">
|
||||
<text class="conversation-name">{{item.name}}</text>
|
||||
<view class="conversation-meta">
|
||||
<text class="conversation-time">{{item.lastMessageTime}}</text>
|
||||
<text class="mute-icon" wx:if="{{item.isMuted}}">🔇</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="conversation-preview">
|
||||
<text class="last-message">{{item.lastMessage}}</text>
|
||||
<view class="message-indicators">
|
||||
<!-- 消息状态指示器 -->
|
||||
<text class="status-indicator" wx:if="{{item.messageStatus === 'sent'}}">✓</text>
|
||||
<text class="status-indicator read" wx:if="{{item.messageStatus === 'read'}}">✓✓</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" wx:if="{{!loading && filteredConversations.length === 0}}">
|
||||
<text class="empty-icon">💬</text>
|
||||
<text class="empty-title">暂无聊天</text>
|
||||
<text class="empty-subtitle">开始一段新的对话吧</text>
|
||||
</view>
|
||||
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
781
pages/message/message.wxss
Normal file
781
pages/message/message.wxss
Normal file
|
|
@ -0,0 +1,781 @@
|
|||
/* 🎨 现代化消息页面设计 - 深色主题 */
|
||||
|
||||
/* 🎨 CSS变量定义 - 黑色主题 */
|
||||
page {
|
||||
--primary-color: #0A84FF;
|
||||
--primary-light: #3EA8FF;
|
||||
--primary-dark: #0056CC;
|
||||
--background-color: #070709;
|
||||
--surface-color: #0F0F11;
|
||||
--text-primary: #ECECEC;
|
||||
--text-secondary: #A8A8A8;
|
||||
--text-tertiary: #7A7A7A;
|
||||
--border-color: rgba(255,255,255,0.06);
|
||||
--shadow-light: 0 1rpx 6rpx rgba(0,0,0,0.6);
|
||||
--shadow-medium: 0 6rpx 18rpx rgba(0,0,0,0.7);
|
||||
--radius-small: 8rpx;
|
||||
--radius-medium: 12rpx;
|
||||
--radius-large: 20rpx;
|
||||
--safe-area-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
/* 🌙 深色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
page {
|
||||
--primary-color: #0A84FF;
|
||||
--primary-light: #64D2FF;
|
||||
--primary-dark: #0056CC;
|
||||
--background-color: #000000;
|
||||
--surface-color: #1C1C1E;
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: #8E8E93;
|
||||
--text-tertiary: #48484A;
|
||||
--border-color: #38383A;
|
||||
--shadow-light: 0 1rpx 3rpx rgba(0, 0, 0, 0.3);
|
||||
--shadow-medium: 0 4rpx 12rpx rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.telegram-container {
|
||||
min-height: 100vh;
|
||||
/* height: calc( 100vh - 250px); */
|
||||
background: var(--background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: var(--safe-area-bottom);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
color: var(--text-primary);
|
||||
transition: background-color 280ms ease, color 280ms ease, background 280ms ease;
|
||||
/* border: 10rpx solid red; */
|
||||
font-weight: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
/* 关键区域同步过渡,避免突变 */
|
||||
.search-section,
|
||||
.search-bar,
|
||||
.conversation-item,
|
||||
.empty-state,
|
||||
.telegram-header {
|
||||
transition: background-color 280ms ease, color 280ms ease, border-color 280ms ease, box-shadow 280ms ease;
|
||||
}
|
||||
.telegram-header{
|
||||
/* border: 1px solid red; */
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
margin-top: 20rpx;
|
||||
|
||||
}
|
||||
.Search-header-left{
|
||||
width: 640rpx;
|
||||
height: 80rpx;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
.Search-input{
|
||||
width: 80%;
|
||||
/* border: 1px solid red; */
|
||||
margin-left: 30rpx;
|
||||
color: #000000; /* 🔥 文字颜色设置为黑色 */
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 🔥 搜索框占位符颜色 */
|
||||
.Search-input::placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.Search-img{
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
.Search-img-add{
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
vertical-align: bottom;
|
||||
margin-left: 20rpx;
|
||||
|
||||
}
|
||||
.group-chat{
|
||||
height: 120rpx;
|
||||
/* border: 1px solid red; */
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
background-color: #1A1A1A;
|
||||
|
||||
|
||||
}
|
||||
.group-chat-conten{
|
||||
/* border: 1px solid red; */
|
||||
display: flex;
|
||||
justify-content:space-around;
|
||||
align-items: center;
|
||||
height: 100rpx;
|
||||
width: 660rpx;
|
||||
}
|
||||
.chat-left{
|
||||
/* border: 1px solid red; */
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
align-items: center;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background-color:#212121;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
.chat-text{
|
||||
width: 500rpx;
|
||||
height: 76rpx;
|
||||
display: flex;
|
||||
justify-content:space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.titel{
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.chat-img{
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
.chat-img-rigth{
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
margin-right: 50rpx;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.chat-conten{
|
||||
/* border: 1px solid red; */
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 100rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.conten-area{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 130rpx;
|
||||
background-color:#212121;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 0;
|
||||
margin-top: 10rpx;
|
||||
width: 650rpx;
|
||||
margin-left: 50rpx;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
|
||||
.conten-area-img{
|
||||
height: 100rpx;
|
||||
width:100rpx;
|
||||
border-radius: 24rpx;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.conten-area-center{
|
||||
|
||||
width: 500rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: 20rpx;
|
||||
flex: 1;
|
||||
}
|
||||
.center-titel{
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.header-content {
|
||||
height: 66rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start; /* 改为起始对齐,按钮通过 margin-left:auto 靠右 */
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
text-shadow: 0 1rpx 2rpx rgba(0,0,0,0.4);
|
||||
}
|
||||
.center-time{
|
||||
/* border: 1px solid red; */
|
||||
margin-right: 40rpx;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-left: auto; /* 将按钮组推到最右侧 */
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
width: 50rpx; /* 与图标一致,避免可见留白 */
|
||||
height: 50rpx;
|
||||
border-radius: 8rpx; /* 小幅圆角以贴合图片边缘 */
|
||||
background: transparent; /* 透明,无底色 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease; /* 去掉背景过渡,仅保留轻微缩放反馈 */
|
||||
}
|
||||
|
||||
.header-btn:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 🎨 现代化内容区域 */
|
||||
.telegram-content {
|
||||
/* flex: 1; */
|
||||
height: 90vh;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
|
||||
/* 让内容自然流式布局,避免覆盖搜索栏与头部 */
|
||||
/* position: relative; */
|
||||
/* border: 10px solid greenyellow; */
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
padding-bottom: 260rpx
|
||||
}
|
||||
|
||||
.telegram-content::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 🎨 现代化搜索栏 */
|
||||
.search-section {
|
||||
padding: 20rpx 32rpx;
|
||||
background: var(--background-color);
|
||||
border-bottom: 1rpx solid var(--border-color);
|
||||
animation: searchSlideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes searchSlideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
height: 80rpx;
|
||||
background: var(--background-color);
|
||||
border: 1rpx solid var(--border-color);
|
||||
border-radius: var(--radius-large);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 28rpx;
|
||||
gap: 16rpx;
|
||||
box-shadow: var(--shadow-light);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-bar:focus-within {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 6rpx rgba(10,132,255,0.06);
|
||||
}
|
||||
|
||||
.search-icon { color: var(--text-secondary); font-size:32rpx; }
|
||||
.search-input { flex:1; font-size:32rpx; color:var(--text-primary); background:transparent }
|
||||
.search-input::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.search-cancel {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: var(--radius-small);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.search-cancel:active {
|
||||
background: rgba(0, 122, 255, 0.1);
|
||||
}
|
||||
|
||||
.cancel-text {
|
||||
font-size: 32rpx;
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 🎨 现代化会话列表 */
|
||||
.conversations-section {
|
||||
background: transparent;
|
||||
border-radius: var(--radius-large) var(--radius-large) 0 0;
|
||||
margin-top: 16rpx;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
padding-bottom: 200rpx; /* 保留空间给底部操作栏,避免白条 */
|
||||
}
|
||||
|
||||
/* 更紧凑 + 国际化(略微灰背景 & 居中收窄) */
|
||||
.conversation-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 22rpx 28rpx; /* 缩小整体高度与左右留白 */
|
||||
transition: all 0.22s ease;
|
||||
position: relative;
|
||||
background: rgba(255,255,255,0.05); /* 更明显的浅灰块(暗色) */
|
||||
margin: 0 auto 16rpx; /* 底部分隔形成卡片间距 */
|
||||
width: 100%;
|
||||
max-width: 1180rpx; /* 大屏时不过宽 */
|
||||
border-radius: 20rpx; /* 轻圆角 */
|
||||
backdrop-filter: blur(3px);
|
||||
-webkit-backdrop-filter: blur(3px);
|
||||
border: 1rpx solid rgba(255,255,255,0.10); /* 明确边界 */
|
||||
box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.35), 0 4rpx 14rpx -4rpx rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.conversation-item:active { background: rgba(255,255,255,0.08); transform: scale(0.992); }
|
||||
|
||||
.conversation-item:last-child { border-bottom: none; }
|
||||
|
||||
/* 🎨 现代化头像设计 */
|
||||
.conversation-avatar { position: relative; margin-right: 28rpx; }
|
||||
.avatar-image { width:108rpx; height:108rpx; border-radius:54rpx; box-shadow: var(--shadow-light); border: 2rpx solid rgba(255,255,255,0.03); }
|
||||
.avatar-placeholder { width:108rpx; height:108rpx; border-radius:54rpx; background: linear-gradient(135deg, #151516 0%, #0F0F11 100%); display:flex; align-items:center; justify-content:center; box-shadow: var(--shadow-light); }
|
||||
.avatar-text { font-size:40rpx; font-weight:600; color:var(--text-primary); }
|
||||
|
||||
/* 🔥 在线状态角标(右下角小圆点)- 增强视觉效果 */
|
||||
.presence-indicator {
|
||||
position: absolute;
|
||||
right: 2rpx;
|
||||
bottom: 2rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid var(--surface-color);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.4);
|
||||
z-index: 3;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 在线状态:亮绿色 + 发光效果 */
|
||||
.presence-indicator.online {
|
||||
background: #2ECC71;
|
||||
box-shadow: 0 0 12rpx rgba(46, 204, 113, 0.6), 0 2rpx 8rpx rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* 离线状态:灰色 + 降低透明度 */
|
||||
.presence-indicator.offline {
|
||||
background: #9E9E9E;
|
||||
opacity: 0.7;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* 🎨 现代化未读徽章 */
|
||||
.unread-badge {
|
||||
position: absolute;
|
||||
top: -6rpx;
|
||||
right: -6rpx;
|
||||
min-width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 22rpx;
|
||||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3rpx solid var(--surface-color);
|
||||
box-shadow: 0 6rpx 18rpx rgba(0,0,0,0.6);
|
||||
z-index: 2;
|
||||
}
|
||||
.unread-count { font-size:22rpx; font-weight:700; color:white }
|
||||
|
||||
/* 🎨 现代化会话信息 */
|
||||
.conversation-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.conversation-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.conversation-name {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 34rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
/* 允许在 flex 容器内正确触发省略号 */
|
||||
min-width: 0;
|
||||
margin-right: 16rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.conversation-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
/* 保留时间+静音的显示空间,避免被名称挤没 */
|
||||
flex: 0 0 auto;
|
||||
flex-shrink: 0;
|
||||
min-width: 120rpx;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.conversation-time {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
/* 避免被压缩隐藏 */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mute-icon {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-tertiary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 🎨 现代化会话预览 */
|
||||
.conversation-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.last-message {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-indicators {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
color: var(--text-secondary);
|
||||
font-size: 20rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.status-indicator.read {
|
||||
color: var(--primary-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 🎨 现代化空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64rpx;
|
||||
color: rgba(255,255,255,0.06);
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-subtitle {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 📱 响应式设计 - 适配不同屏幕尺寸 */
|
||||
@media screen and (max-width: 375px) {
|
||||
.conversation-item {
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.avatar-image, .avatar-placeholder {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 48rpx;
|
||||
}
|
||||
|
||||
.conversation-name {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.last-message {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 414px) {
|
||||
.conversation-item {
|
||||
padding: 24rpx 30rpx; /* 即便大屏也保持克制 */
|
||||
}
|
||||
|
||||
.avatar-image, .avatar-placeholder {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
}
|
||||
|
||||
.conversation-name {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.last-message {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 🎨 会话项增强功能 */
|
||||
.conversation-item.pinned {
|
||||
background: linear-gradient(90deg, rgba(0, 122, 255, 0.05) 0%, transparent 100%);
|
||||
border-left: 4rpx solid var(--primary-color);
|
||||
}
|
||||
|
||||
.conversation-item.muted .conversation-name {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.conversation-item.muted .last-message {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 🎨 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid var(--border-color);
|
||||
border-top: 4rpx solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 🎨 搜索结果高亮 */
|
||||
.search-highlight {
|
||||
background: linear-gradient(135deg, rgba(255, 235, 59, 0.3) 0%, rgba(255, 193, 7, 0.3) 100%);
|
||||
border-radius: 4rpx;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
/* ====================================
|
||||
🔍 云端搜索结果面板样式
|
||||
==================================== */
|
||||
|
||||
/* 清除按钮 */
|
||||
.Search-clear {
|
||||
position: absolute;
|
||||
right: 80rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.Search-clear:active {
|
||||
transform: scale(0.9);
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.Search-header-left {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 搜索结果面板 */
|
||||
.search-results-panel {
|
||||
background: var(--surface-color);
|
||||
border-bottom: 1rpx solid var(--border-color);
|
||||
max-height: 600rpx;
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
max-height: 600rpx;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索结果头部 */
|
||||
.search-results-header {
|
||||
padding: 24rpx 32rpx 16rpx;
|
||||
border-bottom: 1rpx solid var(--border-color);
|
||||
}
|
||||
|
||||
.results-title {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 搜索结果列表 */
|
||||
.search-results-list {
|
||||
max-height: 500rpx;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* 搜索结果项 */
|
||||
.search-result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
background: var(--surface-color);
|
||||
border-bottom: 1rpx solid var(--border-color);
|
||||
transition: background-color 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-result-item:active {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* 结果头像 */
|
||||
.search-result-item .result-avatar {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
margin-right: 24rpx;
|
||||
background: linear-gradient(135deg, #1B1B1D 0%, #101012 100%);
|
||||
}
|
||||
|
||||
.search-result-item .result-avatar .avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.search-result-item .result-avatar .avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 结果信息 */
|
||||
.result-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.result-count {
|
||||
font-size: 24rpx;
|
||||
color: var(--primary-color);
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.result-preview {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 结果箭头 */
|
||||
.result-arrow {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 16rpx;
|
||||
font-size: 48rpx;
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 空结果提示 */
|
||||
.search-results-empty {
|
||||
padding: 80rpx 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-results-empty text {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue