优化了样式,抽象出了 WXSS/JS 库
This commit is contained in:
@@ -1,31 +1,31 @@
|
||||
<view class="chat-page">
|
||||
<scroll-view class="msg-list" scroll-y="true"
|
||||
<scroll-view class="chat-msg-list" scroll-y="true"
|
||||
scroll-into-view="{{scrollToView}}"
|
||||
refresher-enabled="{{true}}"
|
||||
refresher-triggered="{{isRefreshing}}"
|
||||
bindrefresherrefresh="onPullDownRefresh"
|
||||
style="top: 20rpx; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 20rpx);">
|
||||
<view class="loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
|
||||
<view class="chat-loading-more" wx:if="{{loadingHistory}}">加载更早消息...</view>
|
||||
<block wx:for="{{messages}}" wx:key="messageId">
|
||||
<view class="msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
|
||||
<view class="time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
|
||||
<view class="msg-row {{item.senderId === currentUser.id ? 'right' : 'left'}}">
|
||||
<image wx:if="{{item.senderId !== currentUser.id}}" class="avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
|
||||
<view class="bubble {{item.senderId === currentUser.id ? 'bubble-right' : 'bubble-left'}}"
|
||||
<view class="chat-msg-wrapper" data-messageid="{{item.messageId}}" bindlongpress="showAction">
|
||||
<view class="chat-time-tag" wx:if="{{item.showTime}}">{{item.formattedTime}}</view>
|
||||
<view class="chat-msg-row {{item.senderId === currentUser.id ? 'chat-msg-row--right' : 'chat-msg-row--left'}}">
|
||||
<image wx:if="{{item.senderId !== currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || toAvatar}}" mode="aspectFill" binderror="onPeerAvatarError" />
|
||||
<view class="chat-bubble {{item.senderId === currentUser.id ? 'chat-bubble--right' : 'chat-bubble--left'}}"
|
||||
data-messageid="{{item.messageId}}"
|
||||
data-text="{{item.type==='text' ? item.payload.text : ''}}"
|
||||
bindtap="{{item.type==='text' ? 'onBubbleTap' : (item.type==='image' ? 'previewImage' : '')}}"
|
||||
data-url="{{item.type==='image' ? item.payload.url : ''}}">
|
||||
<text wx:if="{{item.type === 'text'}}" class="msg-text">{{item.payload.text}}</text>
|
||||
<image wx:elif="{{item.type === 'image'}}" class="msg-image" src="{{item.payload.url}}" mode="widthFix" />
|
||||
<view wx:elif="{{item.type === 'order'}}" class="order-bubble">
|
||||
<text class="order-label">[订单]</text>
|
||||
<text class="order-info">ID: {{item.payload.orderId}}</text>
|
||||
<text wx:if="{{item.type === 'text'}}" class="chat-msg-text">{{item.payload.text}}</text>
|
||||
<image wx:elif="{{item.type === 'image'}}" class="chat-msg-image" src="{{item.payload.url}}" mode="widthFix" />
|
||||
<view wx:elif="{{item.type === 'order'}}" class="chat-order-bubble">
|
||||
<text class="chat-order-label">[订单]</text>
|
||||
<text class="chat-order-info">ID: {{item.payload.orderId}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<image wx:if="{{item.senderId === currentUser.id}}" class="avatar" src="{{item.senderData.avatar || currentUser.avatar}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
|
||||
<image wx:if="{{item.senderId === currentUser.id}}" class="chat-avatar" src="{{item.senderData.avatar || currentUser.avatar}}" mode="aspectFill" binderror="onSelfAvatarError" data-index="{{index}}" />
|
||||
</view>
|
||||
<view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="read-tag">
|
||||
<view wx:if="{{item.senderId === currentUser.id && item.status === 'success'}}" class="chat-read-tag">
|
||||
{{item.read ? '已读' : '未读'}}
|
||||
</view>
|
||||
</view>
|
||||
@@ -33,53 +33,53 @@
|
||||
<view id="msg-bottom" style="height:20rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
|
||||
<view class="plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
|
||||
<view class="plus-grid">
|
||||
<view class="plus-item" catchtap="openEmojiFromPlus">
|
||||
<text class="plus-icon">😀</text>
|
||||
<text class="plus-label">表情</text>
|
||||
<view class="chat-input-area" style="bottom: {{keyboardHeight}}px; padding-bottom: calc(10rpx + env(safe-area-inset-bottom));">
|
||||
<view class="chat-plus-panel {{showPlusPanel ? 'show' : ''}}" catchtap="closePlusPanel">
|
||||
<view class="chat-plus-grid">
|
||||
<view class="chat-plus-item" catchtap="openEmojiFromPlus">
|
||||
<text class="chat-plus-icon">😀</text>
|
||||
<text class="chat-plus-label">表情</text>
|
||||
</view>
|
||||
<view class="plus-item" catchtap="chooseImage">
|
||||
<text class="plus-icon">📷</text>
|
||||
<text class="plus-label">图片</text>
|
||||
<view class="chat-plus-item" catchtap="chooseImage">
|
||||
<text class="chat-plus-icon">📷</text>
|
||||
<text class="chat-plus-label">图片</text>
|
||||
</view>
|
||||
<view class="plus-item" catchtap="openOrderSender">
|
||||
<text class="plus-icon">📋</text>
|
||||
<text class="plus-label">订单</text>
|
||||
<view class="chat-plus-item" catchtap="openOrderSender">
|
||||
<text class="chat-plus-icon">📋</text>
|
||||
<text class="chat-plus-label">订单</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
|
||||
<view class="emoji-panel {{showEmojiPanel ? 'show' : ''}}">
|
||||
<scroll-view scroll-y="true" class="emoji-scroll">
|
||||
<view class="emoji-grid">
|
||||
<view class="chat-emoji-mask" wx:if="{{showEmojiPanel}}" catchtap="closeEmojiPanel"></view>
|
||||
<view class="chat-emoji-panel {{showEmojiPanel ? 'show' : ''}}">
|
||||
<scroll-view scroll-y="true" class="chat-emoji-scroll">
|
||||
<view class="chat-emoji-grid">
|
||||
<block wx:for="{{emojiList}}" wx:key="*this">
|
||||
<view class="emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
|
||||
<view class="chat-emoji-item" data-emoji="{{item}}" catchtap="insertEmoji">{{item}}</view>
|
||||
</block>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="pending-image" wx:if="{{pendingImage}}">
|
||||
<image class="pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
|
||||
<view class="pending-remove" catchtap="clearPendingImage">✕</view>
|
||||
<view class="chat-pending-image" wx:if="{{pendingImage}}">
|
||||
<image class="chat-pending-thumb" src="{{pendingImage}}" mode="aspectFill" />
|
||||
<view class="chat-pending-remove" catchtap="clearPendingImage">✕</view>
|
||||
</view>
|
||||
|
||||
<view class="write-row">
|
||||
<input class="text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
|
||||
<view class="send-btn" bindtap="sendMessage">发送</view>
|
||||
<view class="plus-btn" bindtap="togglePlusPanel">
|
||||
<text class="plus-icon-single">➕</text>
|
||||
<view class="chat-write-row">
|
||||
<input class="chat-text-input" placeholder="消息" value="{{inputText}}" bindinput="onInput" adjust-position="{{false}}" bindkeyboardheightchange="onKeyboardHeightChange" cursor-spacing="10"/>
|
||||
<view class="chat-send-btn" bindtap="sendMessage">发送</view>
|
||||
<view class="chat-plus-btn" bindtap="togglePlusPanel">
|
||||
<text class="chat-plus-icon-single">➕</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
|
||||
<view class="detail-content" catchtap="noop">
|
||||
<text class="detail-text" user-select="true">{{detailText}}</text>
|
||||
<button class="copy-btn" bindtap="copyDetail">复制</button>
|
||||
<view class="chat-detail-modal" wx:if="{{showDetailModal}}" catchtap="hideDetail">
|
||||
<view class="chat-detail-content" catchtap="noop">
|
||||
<text class="chat-detail-text" user-select="true">{{detailText}}</text>
|
||||
<button class="chat-copy-btn" bindtap="copyDetail">复制</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -1,98 +1 @@
|
||||
.chat-page { width:100%; height:100vh; background:#f5f5f5; }
|
||||
|
||||
.msg-list {
|
||||
position: absolute;
|
||||
left:0; right:0;
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.loading-more { text-align:center; font-size:24rpx; color:#999; padding:20rpx 0; }
|
||||
.time-tag { text-align:center; margin:28rpx 0; font-size:24rpx; color:#b0b0b0; }
|
||||
.msg-wrapper { margin-bottom:12rpx; }
|
||||
.msg-row { display:flex; align-items:flex-start; margin-bottom:4rpx; }
|
||||
.right { justify-content:flex-end; }
|
||||
.left { justify-content:flex-start; }
|
||||
.avatar { width:76rpx; height:76rpx; border-radius:50%; background:#e0e0e0; margin:0 12rpx; flex-shrink:0; }
|
||||
.bubble { max-width:70%; padding:16rpx 20rpx; border-radius:16rpx; position:relative; word-break:break-all; font-size:30rpx; line-height:1.5; }
|
||||
.bubble-left { background:#fff; border-top-left-radius:4rpx; }
|
||||
.bubble-right { background:#00aaff; color:#fff; border-top-right-radius:4rpx; }
|
||||
.msg-text { color:inherit; }
|
||||
.msg-image { max-width:240rpx; border-radius:10rpx; }
|
||||
.order-bubble { background:#fff7e0; padding:12rpx; border-radius:8rpx; }
|
||||
.order-label { font-size:24rpx; color:#e6a23c; font-weight:600; }
|
||||
.order-info { font-size:24rpx; color:#333; margin:6rpx 0; }
|
||||
|
||||
.read-tag {
|
||||
font-size:20rpx;
|
||||
color:#888;
|
||||
text-align:right;
|
||||
padding:4rpx 12rpx 0 0;
|
||||
margin-top: -4rpx;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
position: fixed;
|
||||
left:0; right:0;
|
||||
background:#f7f7f7;
|
||||
border-top:1rpx solid #ddd;
|
||||
z-index:10;
|
||||
}
|
||||
|
||||
.plus-panel {
|
||||
position: absolute;
|
||||
bottom:100%; left:0; right:0;
|
||||
background:#fff;
|
||||
border-top:1rpx solid #e0e0e0;
|
||||
padding: 20rpx;
|
||||
transform: translateY(100%);
|
||||
opacity:0;
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
pointer-events:none;
|
||||
z-index:9;
|
||||
}
|
||||
.plus-panel.show { transform: translateY(0); opacity:1; pointer-events:auto; }
|
||||
.plus-grid { display: flex; justify-content: space-around; }
|
||||
.plus-item { display: flex; flex-direction: column; align-items: center; width: 120rpx; }
|
||||
.plus-icon { font-size: 60rpx; }
|
||||
.plus-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
|
||||
|
||||
.emoji-mask { position:fixed; top:0; left:0; right:0; bottom:0; background:transparent; z-index:8; }
|
||||
.emoji-panel {
|
||||
position: absolute; bottom:100%; left:0; right:0;
|
||||
background:#fff; border-top:1rpx solid #e0e0e0;
|
||||
height:380rpx;
|
||||
transform: translateY(100%); opacity:0; transition:0.3s;
|
||||
pointer-events:none; z-index:9;
|
||||
}
|
||||
.emoji-panel.show { transform:translateY(0); opacity:1; pointer-events:auto; }
|
||||
.emoji-scroll { height:100%; }
|
||||
.emoji-grid { display:flex; flex-wrap:wrap; padding:20rpx 10rpx; }
|
||||
.emoji-item { width:14.28%; text-align:center; padding:16rpx 0; font-size:44rpx; }
|
||||
|
||||
.pending-image { display:flex; align-items:center; padding:10rpx 20rpx; background:#fff; }
|
||||
.pending-thumb { width:80rpx; height:80rpx; border-radius:8rpx; }
|
||||
.pending-remove { font-size:30rpx; color:#999; margin-left:10rpx; }
|
||||
|
||||
.write-row { display:flex; padding:0 20rpx 10rpx; align-items:center; }
|
||||
.text-input {
|
||||
flex:1; height:76rpx; background:#fff; border-radius:12rpx;
|
||||
padding:0 20rpx; font-size:30rpx; margin-right:12rpx;
|
||||
}
|
||||
.send-btn {
|
||||
width:120rpx; height:76rpx; background:#00aaff; color:#fff;
|
||||
font-size:28rpx; font-weight:500; border-radius:12rpx;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
}
|
||||
.plus-btn {
|
||||
width:56rpx; height:56rpx;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
.plus-icon-single { font-size:46rpx; color:#00aaff; }
|
||||
|
||||
.detail-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:999; display:flex; align-items:center; justify-content:center; }
|
||||
.detail-content { width:80%; max-height:80%; background:#fff; border-radius:16rpx; padding:40rpx; }
|
||||
.detail-text { font-size:32rpx; line-height:1.6; word-break:break-all; overflow-y:auto; }
|
||||
.copy-btn { width:100%; height:80rpx; background:#00aaff; color:#fff; border-radius:12rpx; margin-top:20rpx; font-size:30rpx; }
|
||||
/* 使用全局 chat-* 样式 */
|
||||
Reference in New Issue
Block a user