858 lines
24 KiB
Vue
858 lines
24 KiB
Vue
<template>
|
||
<view>
|
||
<view v-for="(message, index) in displayList" :key="message.id || index" class="message-item" :class="{
|
||
isMe: message.isMe,
|
||
'm-t-16': shouldApplyMt16(index) && !shouldShowTime(index),
|
||
'is-sort-mode': sortMode,
|
||
'sort-dragging': sortMode && dragIndex === index,
|
||
'sort-drop-before': sortMode && dragOverIndex === index && dragIndex !== index && dropPosition === 'before',
|
||
'sort-drop-after': sortMode && dragOverIndex === index && dragIndex !== index && dropPosition === 'after'
|
||
}">
|
||
|
||
<!-- 排序模式下的拖拽手柄 -->
|
||
<view v-if="sortMode" class="sort-handle-wrap" @longpress="onSortLongPress(index, $event)"
|
||
@touchmove.stop.prevent="onSortTouchMove(index, $event)" @touchend.stop="onSortTouchEnd(index, $event)">
|
||
<view class="sort-handle-bar"></view>
|
||
<view class="sort-handle-bar"></view>
|
||
<view class="sort-handle-bar"></view>
|
||
</view>
|
||
|
||
<view style="flex: 1; overflow: hidden;">
|
||
<view class="time m-t-44" v-if="shouldShowTime(index)">
|
||
<view class="top-text" v-if="phone == 'iphone' && index == 0">信息 · 短信</view>
|
||
<view class="top-text" v-if="phone == 'huawei' && index == 0">短信/彩信</view>
|
||
<text v-if="phone == 'huawei'">{{ formatHuaweiTopTime(message.time) }}</text>
|
||
<text v-else>{{ formatChatTime(message.time) }} <text
|
||
v-if="(phone == 'oppo' || (phone == 'vivo' && message.isMe)) && message.simIndex"> 中国联通
|
||
</text></text>
|
||
|
||
<image style="width: 20rpx;height: 24rpx;margin-left: 8rpx; "
|
||
v-if="phone == 'oppo' || (phone == 'vivo' && message.isMe)"
|
||
:src="`/static/image/phone-message/huawei/chat-ka${message.simIndex}.png`">
|
||
</image>
|
||
</view>
|
||
<view class="chat-box" :id="'msg-' + index" :class="{
|
||
'tail-right': shouldApplyTailRight(index),
|
||
'tail-left': shouldApplyTailLeft(index),
|
||
'delivered': isLastMeMessage(index)
|
||
}" @longpress="!sortMode && onMessageLongPress(index, message)">
|
||
<text v-if="message.isMe && phone == 'mi'" class="send-text">送达</text>
|
||
<view class="chat-bubble">
|
||
<view v-html="formatMessageContent(message.content, message.isMe)"></view>
|
||
</view>
|
||
</view>
|
||
<view v-if="phone == 'huawei'" class="second-info">
|
||
<text>{{ formatHuaweiBottomTime(message.time) }}</text>
|
||
<image :src="`/static/image/phone-message/huawei/chat-ka${message.simIndex}.png`"></image>
|
||
</view>
|
||
<view v-if="(phone == 'oppo' || phone == 'vivo') && message.isMe" class="second-info">
|
||
<text v-if="message.isMe" class="delivered">已送达</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
|
||
import { onLoad, onPageScroll } from "@dcloudio/uni-app";
|
||
import { stringUtil, util } from '@/utils/common.js';
|
||
const props = defineProps({
|
||
// 手机品牌
|
||
phone: {
|
||
type: String,
|
||
default: 'iphone'
|
||
},
|
||
messageList: {
|
||
type: Array,
|
||
default: []
|
||
},
|
||
// 是否处于拖拽排序模式
|
||
sortMode: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['onMessageLongPress', 'sort'])
|
||
|
||
// 排序模式下的本地唦本列表
|
||
// 不直接修改 props.messageList,排序完成后 emit 给父组件
|
||
const localSortList = ref([])
|
||
const dragIndex = ref(-1)
|
||
const dragOverIndex = ref(-1)
|
||
const dropPosition = ref('after') // 'before'=插入目标上方, 'after'=插入目标下方
|
||
let isDragging = false
|
||
let sortItemRects = []
|
||
|
||
// 当 sortMode 切换时同步本地副本
|
||
watch(() => props.sortMode, (val) => {
|
||
if (val) {
|
||
localSortList.value = props.messageList.map(item => ({ ...item }))
|
||
} else {
|
||
dragIndex.value = -1
|
||
dragOverIndex.value = -1
|
||
isDragging = false
|
||
sortItemRects = []
|
||
localSortList.value = []
|
||
}
|
||
})
|
||
|
||
// 实际渲染用的列表:排序模式下用内部副本,否则用原始列表
|
||
const displayList = computed(() => {
|
||
if (props.sortMode) return localSortList.value
|
||
return props.messageList
|
||
})
|
||
|
||
// 判断消息isMe==true时上一条消息是否也是isMe==true,如果不是则应用m-t-16样式
|
||
const shouldApplyMt16 = (index) => {
|
||
if (index === 0) return false;
|
||
const currentMsg = displayList.value[index];
|
||
const prevMsg = displayList.value[index - 1];
|
||
if (currentMsg.isMe && !prevMsg.isMe) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 判断消息isMe==true时下一条消息是否也是isMe==true,如果不是则应用m-t-16样式
|
||
const shouldApplyNextIsMe = (index) => {
|
||
const currentMsg = displayList.value[index];
|
||
// 拦截:如果已经是数组的最后一条消息,必然不存在下一条消息
|
||
if (index >= displayList.value.length - 1) return false;
|
||
|
||
const nextMsg = displayList.value[index + 1];
|
||
if (currentMsg.isMe && nextMsg.isMe) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 判断是否为最后一条自己发出的消息
|
||
const isLastMeMessage = (currentIndex) => {
|
||
const currentMsg = displayList.value[currentIndex];
|
||
if (!currentMsg.isMe) return false;
|
||
|
||
// 往后遍历,如果还能找到isMe=true的消息,说明当前这条不是最后一条
|
||
for (let i = currentIndex + 1; i < displayList.value.length; i++) {
|
||
if (displayList.value[i].isMe) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 判断 tail-right(右侧我方气泡尾巴)样式是否生效
|
||
const shouldApplyTailRight = (index) => {
|
||
const currentMsg = displayList.value[index];
|
||
if (!currentMsg.isMe) return false; // 不是我发的直接没右侧尾巴
|
||
|
||
// 如果这是整体列表最后一条消息,必然有尾巴
|
||
if (index === displayList.value.length - 1) return true;
|
||
|
||
const nextMsg = displayList.value[index + 1];
|
||
|
||
// 条件 c: 下一条消息 isMe == false (也就是被打断了)
|
||
if (!nextMsg.isMe) return true;
|
||
|
||
// 条件 a: 下一条消息间隔三分钟以上 (180000 毫秒) - 时间分割线导致当前段落结束
|
||
const currentMsgTime = new Date(currentMsg.time.replace(/-/g, '/')).getTime();
|
||
const nextMsgTime = new Date(nextMsg.time.replace(/-/g, '/')).getTime();
|
||
if (!isNaN(currentMsgTime) && !isNaN(nextMsgTime) && (nextMsgTime - currentMsgTime > 180000)) {
|
||
return true;
|
||
}
|
||
|
||
// 其他情况(连绵不断的连发,中间没超时间也没被对方打断),则隐藏中间环节气泡尾巴
|
||
return false;
|
||
}
|
||
|
||
// 判断 tail-left 样式是否生效
|
||
const shouldApplyTailLeft = (index) => {
|
||
const currentMsg = displayList.value[index];
|
||
if (currentMsg.isMe) return false;
|
||
|
||
// 如果这是整体列表最后一条消息,也就符合最后一条isMe==false的特征
|
||
if (index === displayList.value.length - 1) return true;
|
||
|
||
const nextMsg = displayList.value[index + 1];
|
||
|
||
// 条件 c: 下一条消息 isMe == true
|
||
if (nextMsg.isMe) return true;
|
||
|
||
// 条件 a: 下一条消息间隔三分钟以上 (180000 毫秒)
|
||
const currentMsgTime = new Date(currentMsg.time.replace(/-/g, '/')).getTime();
|
||
const nextMsgTime = new Date(nextMsg.time.replace(/-/g, '/')).getTime();
|
||
if (!isNaN(currentMsgTime) && !isNaN(nextMsgTime) && (nextMsgTime - currentMsgTime > 180000)) {
|
||
return true;
|
||
}
|
||
|
||
// 条件 b: 这是最后一条 isMe == false 的消息
|
||
for (let i = index + 1; i < displayList.value.length; i++) {
|
||
if (!displayList.value[i].isMe) {
|
||
return false; // 往后还有不是自己发的消息,所以当前并非最后一条 isMe==false
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 获取格式化后的聊天时间显示
|
||
const formatChatTime = (timeStr) => {
|
||
if (!timeStr) return '';
|
||
const date = new Date(timeStr.replace(/-/g, '/'));
|
||
if (isNaN(date.getTime())) return timeStr;
|
||
|
||
const now = new Date();
|
||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||
const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||
|
||
const diffDays = Math.floor((today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24));
|
||
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
const timeNum = `${hours}:${minutes}`;
|
||
|
||
if (diffDays === 0) {
|
||
if (props.phone == 'iphone' || props.phone == 'oppo') return `今天 ${timeNum}`;
|
||
return `${timeNum}`;
|
||
} else if (diffDays === 1) {
|
||
return `昨天 ${timeNum}`;
|
||
} else if (diffDays > 1 && diffDays < 7) {
|
||
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||
return `${weekDays[date.getDay()]} ${timeNum}`;
|
||
} else {
|
||
// 超过一周,同一年显示 月日 时分,跨年显示 年月日 时分
|
||
if (date.getFullYear() === now.getFullYear()) {
|
||
return `${date.getMonth() + 1}月${date.getDate()}日 ${timeNum}`;
|
||
} else {
|
||
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${timeNum}`;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 华为顶部时间栏:只显示日期维度的信息,无具体时分秒
|
||
const formatHuaweiTopTime = (timeStr) => {
|
||
if (!timeStr) return '';
|
||
const date = new Date(timeStr.replace(/-/g, '/'));
|
||
if (isNaN(date.getTime())) return timeStr;
|
||
|
||
const now = new Date();
|
||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||
const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||
const diffDays = Math.floor((today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24));
|
||
|
||
if (diffDays === 0) {
|
||
return `今天`;
|
||
} else if (diffDays === 1) {
|
||
return `昨天`;
|
||
} else {
|
||
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||
const weekdayStr = weekDays[date.getDay()];
|
||
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日${weekdayStr}`;
|
||
}
|
||
}
|
||
|
||
// 华为底部小尾巴附加时间:上下午时分、或者“刚刚”
|
||
const formatHuaweiBottomTime = (timeStr) => {
|
||
if (!timeStr) return '';
|
||
const date = new Date(timeStr.replace(/-/g, '/'));
|
||
if (isNaN(date.getTime())) return timeStr;
|
||
|
||
const now = new Date();
|
||
// 判断是否在一分钟内(差值在 60000 毫秒以内)
|
||
const diffMs = now.getTime() - date.getTime();
|
||
if (diffMs >= 0 && diffMs <= 60000) {
|
||
return '刚刚';
|
||
}
|
||
|
||
const ampm = date.getHours() >= 12 ? '下午' : '上午';
|
||
// 转换为 12 小时制
|
||
let displayHour = date.getHours() % 12;
|
||
displayHour = displayHour ? displayHour : 12; // 如果是 0(午夜) 则是12
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
|
||
return `${ampm}${displayHour}:${minutes}`;
|
||
}
|
||
|
||
// 判断是否显示时间
|
||
// timeMode: 'show'=强制显示, 'hide'=强制隐藏, 'auto'或无=按间隔逻辑判断
|
||
const shouldShowTime = (index) => {
|
||
const currentMsg = displayList.value[index];
|
||
const mode = currentMsg.timeMode;
|
||
|
||
// 强制隐藏(兼容旧 hideTime 字段)
|
||
if (mode === 'hide' || currentMsg.hideTime) return false;
|
||
|
||
// 强制显示
|
||
if (mode === 'show') return true;
|
||
|
||
// 以下为 auto / 默认 逻辑
|
||
if (index === 0) return true; // 第一条必定显示
|
||
|
||
// 排序模式下每条均显示时间
|
||
if (props.sortMode) return true;
|
||
|
||
const currentMsgTime = new Date(currentMsg.time.replace(/-/g, '/')).getTime();
|
||
const prevMsgTime = new Date(displayList.value[index - 1].time.replace(/-/g, '/')).getTime();
|
||
|
||
if (isNaN(currentMsgTime) || isNaN(prevMsgTime)) return true;
|
||
|
||
// 绝对值比较,兼容排序后时间倒序
|
||
return Math.abs(currentMsgTime - prevMsgTime) > 180000;
|
||
}
|
||
|
||
// 格式化文本:给网址和 5 位以上连续数字加上下划线样式,非我方消息同时赋予文字蓝色
|
||
const formatMessageContent = (content, isMe) => {
|
||
if (!content) return '';
|
||
|
||
// 匹配 url 或 5位以上连续数字
|
||
// URL: http(s)://... 或 xxx.xxx/...
|
||
const combinedRegex = /((?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[a-zA-Z0-9_/%-]*)*)|(\b\d{5,}(?:\.\d+)?\b)/g;
|
||
|
||
return content.replace(combinedRegex, (match, p1, p2) => {
|
||
// 如果是数字且包含小数点(通常是金额),则不添加下划线样式
|
||
if (p2 && p2.includes('.')) {
|
||
return match;
|
||
}
|
||
|
||
let color = '#3A85F8'
|
||
if (props.phone == 'iphone') color = '#0B7AE3'
|
||
if (props.phone == 'mi') color = '#3A85F8'
|
||
const colorStyle = !isMe ? `color: ${color};` : '';
|
||
return `<span style="text-decoration: underline; ${colorStyle}">${match}</span>`;
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* 长按信息
|
||
* @param index
|
||
* @param message
|
||
*/
|
||
const onMessageLongPress = (index, message) => {
|
||
emit('onLongPress', index, message)
|
||
}
|
||
|
||
// ===================== 拖拽排序逻辑 =====================
|
||
|
||
/**
|
||
* 刷新所有排序条目的 rect 坐标缓存
|
||
*/
|
||
const refreshSortRects = (callback) => {
|
||
const query = uni.createSelectorQuery()
|
||
const len = localSortList.value.length
|
||
let collected = []
|
||
let done = 0
|
||
for (let i = 0; i < len; i++) {
|
||
query.select('#msg-' + i).boundingClientRect(rect => {
|
||
collected[i] = rect
|
||
done++
|
||
if (done === len && callback) callback(collected)
|
||
})
|
||
}
|
||
query.exec()
|
||
}
|
||
|
||
/**
|
||
* 长按手柄开始拖拽
|
||
*/
|
||
const onSortLongPress = (idx, e) => {
|
||
dragIndex.value = idx
|
||
dragOverIndex.value = idx
|
||
isDragging = true
|
||
refreshSortRects(rects => {
|
||
sortItemRects = rects
|
||
})
|
||
uni.vibrateShort({ type: 'medium' })
|
||
}
|
||
|
||
/**
|
||
* 拖拽移动 - 根据触摸点 Y 坐标確定悬停位置和插入方向
|
||
*/
|
||
const onSortTouchMove = (idx, e) => {
|
||
if (!isDragging || dragIndex.value === -1) return
|
||
if (!e.touches || !e.touches[0]) return
|
||
const touchY = e.touches[0].clientY
|
||
if (!sortItemRects || sortItemRects.length === 0) return
|
||
let overIdx = dragOverIndex.value
|
||
for (let i = 0; i < sortItemRects.length; i++) {
|
||
const rect = sortItemRects[i]
|
||
if (rect && touchY >= rect.top && touchY <= rect.bottom) {
|
||
overIdx = i
|
||
// 判断手指在该元素的上半区还是下半区
|
||
const mid = rect.top + rect.height / 2
|
||
dropPosition.value = touchY < mid ? 'before' : 'after'
|
||
break
|
||
}
|
||
}
|
||
dragOverIndex.value = overIdx
|
||
}
|
||
|
||
/**
|
||
* 拖拽结束 - 根据 dropPosition 決定插入到目标的上方还是下方
|
||
*/
|
||
const onSortTouchEnd = (idx, e) => {
|
||
if (!isDragging) return
|
||
const from = dragIndex.value
|
||
const to = dragOverIndex.value
|
||
if (from !== -1 && to !== -1 && from !== to) {
|
||
const list = [...localSortList.value]
|
||
const [removed] = list.splice(from, 1)
|
||
// from 被移除后,如果 to 在 from 后面,to 需要 -1
|
||
let insertAt = to > from ? to - 1 : to
|
||
if (dropPosition.value === 'after') insertAt += 1
|
||
list.splice(insertAt, 0, removed)
|
||
localSortList.value = list
|
||
emit('sort', list.map(item => ({ ...item })))
|
||
nextTick(() => {
|
||
refreshSortRects(rects => {
|
||
sortItemRects = rects
|
||
})
|
||
})
|
||
}
|
||
dragIndex.value = -1
|
||
dragOverIndex.value = -1
|
||
dropPosition.value = 'after'
|
||
isDragging = false
|
||
}
|
||
</script>
|
||
<style lang="less" scoped>
|
||
/* ===== 排序模式公共样式(跨所有手机品牌通用)===== */
|
||
/* 排序模式下每条消息左右横排(手柄 + 气泡取剩余宽度)*/
|
||
.is-sort-mode {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: stretch;
|
||
}
|
||
|
||
/* 拖拽手柄区域 */
|
||
.sort-handle-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 60rpx;
|
||
flex-shrink: 0;
|
||
padding: 0 8rpx;
|
||
|
||
.sort-handle-bar {
|
||
margin: 3rpx 0;
|
||
}
|
||
}
|
||
|
||
.sort-handle-bar {
|
||
width: 30rpx;
|
||
height: 4rpx;
|
||
border-radius: 2rpx;
|
||
background-color: #CCCCCC;
|
||
}
|
||
|
||
/* 被拖拽中的条目:高亮蓝边 + 轻微缩进 */
|
||
.sort-dragging {
|
||
opacity: 0.7;
|
||
background-color: rgba(0, 122, 255, 0.06) !important;
|
||
border-left: 4rpx solid #007AFF;
|
||
}
|
||
|
||
/* 插入线:将拖拽项放在目标上方 */
|
||
.sort-drop-before {
|
||
border-top: 3rpx dashed #007AFF !important;
|
||
padding-top: 4rpx;
|
||
}
|
||
|
||
/* 插入线:将拖拽项放在目标下方 */
|
||
.sort-drop-after {
|
||
border-bottom: 3rpx dashed #007AFF !important;
|
||
padding-bottom: 4rpx;
|
||
}
|
||
|
||
// 苹果样式
|
||
.iphone-style {
|
||
.m-t-16 {
|
||
margin-top: 16rpx !important;
|
||
}
|
||
|
||
.top-text {
|
||
text-align: center;
|
||
font-size: 20rpx;
|
||
color: #838383;
|
||
margin-top: 26rpx;
|
||
}
|
||
|
||
.time {
|
||
color: #838383;
|
||
font-size: 20rpx;
|
||
text-align: center;
|
||
margin-bottom: 20rpx;
|
||
margin-top: 36rpx;
|
||
}
|
||
|
||
.chat-box {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
|
||
.chat-bubble {
|
||
padding: 18rpx 22rpx;
|
||
background-color: #E9E9EB;
|
||
border-radius: 34rpx;
|
||
max-width: 600rpx;
|
||
font-size: 32rpx;
|
||
line-height: 38rpx;
|
||
color: #1A1A1A;
|
||
margin: 4rpx 30rpx 0;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
|
||
.isMe {
|
||
.chat-box {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.chat-bubble {
|
||
background-color: #34C85A;
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.tail-left {
|
||
position: relative;
|
||
}
|
||
|
||
.tail-left::after {
|
||
position: absolute;
|
||
left: 18rpx;
|
||
bottom: 0;
|
||
content: '';
|
||
background: url('/static/image/phone-message/iphone/left-msg-box.png') no-repeat center bottom;
|
||
background-size: 100% 100%;
|
||
width: 28rpx;
|
||
height: 36rpx;
|
||
}
|
||
|
||
.tail-right {
|
||
position: relative;
|
||
}
|
||
|
||
.delivered::before {
|
||
position: absolute;
|
||
right: 18rpx;
|
||
bottom: -12rpx;
|
||
content: '已送达';
|
||
font-size: 20rpx;
|
||
line-height: 20rpx;
|
||
color: #8B8B8B;
|
||
transform: translateY(100%);
|
||
}
|
||
|
||
.tail-right::after {
|
||
position: absolute;
|
||
right: 18rpx;
|
||
bottom: 0;
|
||
content: '';
|
||
background: url('/static/image/phone-message/iphone/right-msg-box.png') no-repeat center bottom;
|
||
background-size: 100% 100%;
|
||
width: 28rpx;
|
||
height: 36rpx;
|
||
}
|
||
}
|
||
|
||
// 小米样式
|
||
.mi-style {
|
||
.top-text {
|
||
text-align: center;
|
||
font-size: 20rpx;
|
||
color: #838383;
|
||
margin-top: 26rpx;
|
||
}
|
||
|
||
.time {
|
||
padding: 0 54rpx;
|
||
color: #646464;
|
||
font-size: 24rpx;
|
||
line-height: 28rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.chat-box {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
|
||
.chat-bubble {
|
||
padding: 22rpx 42rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 32rpx;
|
||
font-size: 32rpx;
|
||
line-height: 50rpx;
|
||
color: #1A1A1A;
|
||
max-width: 550rpx;
|
||
margin: 0 24rpx;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
.message-item {
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.m-t-44 {
|
||
margin-top: 44rpx;
|
||
}
|
||
|
||
|
||
.isMe {
|
||
|
||
.time {
|
||
text-align: right;
|
||
}
|
||
|
||
.chat-box {
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
|
||
.send-text {
|
||
color: #646464;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.chat-bubble {
|
||
margin-left: 12rpx;
|
||
background-color: #3681FF;
|
||
color: #FFFFFF;
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|
||
// oppo样式
|
||
.oppo-style {
|
||
.m-t-16 {
|
||
margin-top: 24rpx !important;
|
||
}
|
||
|
||
.time {
|
||
color: #727377;
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
text-align: center;
|
||
margin-bottom: 20rpx;
|
||
margin-top: 48rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.message-item {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.chat-box {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
|
||
.chat-bubble {
|
||
padding: 28rpx 48rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 32rpx;
|
||
max-width: 600rpx;
|
||
font-size: 32rpx;
|
||
line-height: 44rpx;
|
||
color: #1A1A1A;
|
||
margin: 0 32rpx;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
|
||
.isMe {
|
||
.chat-box {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.chat-bubble {
|
||
background-color: #00A1FF;
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.second-info {
|
||
font-size: 24rpx;
|
||
text-align: right;
|
||
padding: 0 34rpx;
|
||
margin-top: 14rpx;
|
||
padding-bottom: 6rpx;
|
||
|
||
.delivered {
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
color: #727377;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
// 华为样式
|
||
.huawei-style {
|
||
.top-text {
|
||
text-align: center;
|
||
font-size: 22rpx;
|
||
line-height: 22rpx;
|
||
color: #545454;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.time {
|
||
text-align: center;
|
||
font-size: 22rpx;
|
||
line-height: 22rpx;
|
||
color: #545454;
|
||
margin-top: 40rpx;
|
||
}
|
||
|
||
.chat-box {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
|
||
.chat-bubble {
|
||
padding: 12rpx 24rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 4rpx 28rpx 28rpx 28rpx;
|
||
max-width: 550rpx;
|
||
margin: 0 30rpx;
|
||
margin-top: 34rpx;
|
||
font-size: 30rpx;
|
||
line-height: 38rpx;
|
||
color: #1a1a1a;
|
||
font-weight: 500;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
.second-info {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #545454;
|
||
font-size: 20rpx;
|
||
line-height: 24rpx;
|
||
margin-top: 16rpx;
|
||
padding-left: 52rpx;
|
||
padding-right: 56rpx;
|
||
|
||
image {
|
||
width: 14rpx;
|
||
height: 18rpx;
|
||
margin-left: 8rpx;
|
||
}
|
||
}
|
||
|
||
|
||
.m-t-44 {
|
||
margin-top: 44rpx;
|
||
}
|
||
|
||
|
||
.isMe {
|
||
.second-info {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.message-item {
|
||
margin-top: 34rpx;
|
||
}
|
||
|
||
.chat-box {
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
|
||
.send-text {
|
||
color: #646464;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.chat-bubble {
|
||
margin-left: 12rpx;
|
||
background-color: #3681FF;
|
||
color: #FFFFFF;
|
||
border-radius: 28rpx 4rpx 28rpx 28rpx;
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|
||
// vivo样式
|
||
.vivo-style {
|
||
.m-t-16 {
|
||
margin-top: 24rpx !important;
|
||
}
|
||
|
||
.time {
|
||
color: #ACACAC;
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
text-align: center;
|
||
margin: 40rpx 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.message-item {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.chat-box {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
width: 100%;
|
||
|
||
.chat-bubble {
|
||
padding: 38rpx 38rpx 32rpx 40rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 32rpx;
|
||
max-width: 100%;
|
||
font-size: 36rpx;
|
||
line-height: 48rpx;
|
||
color: #1A1A1A;
|
||
margin: 0 32rpx;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
|
||
.isMe {
|
||
.chat-box {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.chat-bubble {
|
||
background-color: #0078FE;
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.second-info {
|
||
font-size: 24rpx;
|
||
text-align: right;
|
||
padding: 0 34rpx;
|
||
margin-top: 12rpx;
|
||
padding-bottom: 6rpx;
|
||
|
||
.delivered {
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
color: #ACACAC;
|
||
}
|
||
}
|
||
|
||
}
|
||
</style>
|