Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1
|
|
@ -34,24 +34,45 @@
|
|||
</image>
|
||||
</view>
|
||||
<view class="chat-box" :id="'msg-' + index" :class="{
|
||||
'tail-right': shouldApplyTailRight(index) && !isImageMsg(message),
|
||||
'tail-right': (shouldApplyTailRight(index) || isLastMeMessage(index)) && !isImageMsg(message),
|
||||
'tail-left': shouldApplyTailLeft(index) && !isImageMsg(message),
|
||||
'image-tail-left': shouldApplyTailLeft(index) && isImageMsg(message),
|
||||
'delivered': isLastMeMessage(index)
|
||||
}" @longpress="!sortMode && onMessageLongPress(index, message)">
|
||||
<text v-if="message.isMe && phone == 'mi'" class="send-text">送达</text>
|
||||
|
||||
<view class="chat-bubble" :class="{ 'image-bubble': isImageMsg(message) }">
|
||||
<image v-if="isImageMsg(message)" :src="getImageSrc(message)" mode="aspectFill"
|
||||
class="chat-image" :style="getImageStyle(message)" @tap.stop="handleImageClick(message)">
|
||||
</image>
|
||||
<view v-if="isImageMsg(message)" class="image-wrap" :class="{
|
||||
'image-wrap-left': phone == 'iphone' && shouldApplyTailLeft(index) && isImageMsg(message),
|
||||
'image-wrap-right': phone == 'iphone' && shouldApplyTailRight(index) && isImageMsg(message)
|
||||
}">
|
||||
<image :src="getImageSrc(message)" :mode="getImageMode(message, phone)"
|
||||
class="chat-image" :class="{
|
||||
'chat-image-left': phone == 'iphone' && shouldApplyTailLeft(index) && isImageMsg(message),
|
||||
'chat-image-right': phone == 'iphone' && shouldApplyTailRight(index) && isImageMsg(message)
|
||||
}" :style="phone == 'iphone' ? '' : getImageStyle(message)"
|
||||
@tap.stop="handleImageClick(message)">
|
||||
</image>
|
||||
<image v-if="phone == 'iphone'" :class="{
|
||||
'mask-left-bottom': phone == 'iphone' && shouldApplyTailLeft(index) && isImageMsg(message),
|
||||
'mask-right-bottom': phone == 'iphone' && shouldApplyTailRight(index) && isImageMsg(message)
|
||||
}" style="width: 46rpx;height:10rpx;"
|
||||
src="/static/image/phone-message/iphone/iphone-mask.png"></image>
|
||||
</view>
|
||||
<rich-text v-else :nodes="formatMessageContent(message.content, message.isMe)"></rich-text>
|
||||
</view>
|
||||
<image v-if="shouldApplyTailLeft(index) && isImageMsg(message) && phone == 'iphone'"
|
||||
style="width: 68rpx;height: 68rpx;" src="/static/image/phone-message/iphone/save.png">
|
||||
</image>
|
||||
<!-- <text v-if="message.isMe && phone == 'iphone' && isLastMeMessage(index)"
|
||||
class="send-text">已送达</text> -->
|
||||
</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>
|
||||
<text v-if="message.isMe" class="delivered">{{ phone == 'vivo' ? '已发送' : '已送达' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -68,6 +89,7 @@ const handleImageClick = (message) => {
|
|||
if (!src) return;
|
||||
|
||||
const allImages = [];
|
||||
const allTimes = [];
|
||||
let clickIndex = 0;
|
||||
|
||||
displayList.value.forEach(msg => {
|
||||
|
|
@ -78,12 +100,13 @@ const handleImageClick = (message) => {
|
|||
clickIndex = allImages.length;
|
||||
}
|
||||
allImages.push(imgSrc);
|
||||
allTimes.push(msg.time);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (props.phone === 'oppo') {
|
||||
emit('previewImage', { images: allImages, index: clickIndex });
|
||||
if (props.phone === 'oppo' || props.phone === 'mi') {
|
||||
emit('previewImage', { images: allImages, index: clickIndex, times: allTimes });
|
||||
} else {
|
||||
uni.previewImage({ urls: allImages, current: clickIndex });
|
||||
}
|
||||
|
|
@ -136,9 +159,9 @@ let sortItemRects = []
|
|||
watch(() => props.sortMode, (val) => {
|
||||
if (val) {
|
||||
let list = props.messageList;
|
||||
if (props.phone !== 'oppo') {
|
||||
list = list.filter(msg => !isImageMsg(msg));
|
||||
}
|
||||
// if (props.phone !== 'oppo') {
|
||||
// list = list.filter(msg => !isImageMsg(msg));
|
||||
// }
|
||||
localSortList.value = list.map(item => ({ ...item }))
|
||||
} else {
|
||||
dragIndex.value = -1
|
||||
|
|
@ -154,9 +177,9 @@ const displayList = computed(() => {
|
|||
if (props.sortMode) return localSortList.value
|
||||
|
||||
let list = props.messageList;
|
||||
if (props.phone !== 'oppo') {
|
||||
list = list.filter(msg => !isImageMsg(msg));
|
||||
}
|
||||
// if (props.phone !== 'oppo') {
|
||||
// list = list.filter(msg => !isImageMsg(msg));
|
||||
// }
|
||||
return list;
|
||||
})
|
||||
|
||||
|
|
@ -200,12 +223,8 @@ const shouldApplyTailRight = (index) => {
|
|||
// 条件 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;
|
||||
}
|
||||
// 如果下一条消息上方显示了时间,则当前消息就是这一段的最后一条,应该有尾巴!
|
||||
if (shouldShowTime(index + 1)) return true;
|
||||
|
||||
// 其他情况(连绵不断的连发,中间没超时间也没被对方打断),则隐藏中间环节气泡尾巴
|
||||
return false;
|
||||
|
|
@ -224,21 +243,11 @@ const shouldApplyTailLeft = (index) => {
|
|||
// 条件 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;
|
||||
}
|
||||
// 如果下一条消息上方显示了时间,则当前消息就是这一段的最后一条,应该有尾巴!
|
||||
if (shouldShowTime(index + 1)) 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;
|
||||
// 否则说明后面还有相连的对方消息,不用加尾巴
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取格式化后的聊天时间显示
|
||||
|
|
@ -392,9 +401,55 @@ const getImageSrc = (message) => {
|
|||
return '';
|
||||
}
|
||||
|
||||
// 获取图片渲染模式
|
||||
const getImageMode = (message, phone) => {
|
||||
if (phone === 'iphone') return 'widthFix';
|
||||
if (phone === 'huawei') {
|
||||
const w = parseInt(message.imgWidth);
|
||||
const h = parseInt(message.imgHeight);
|
||||
if (!isNaN(w) && !isNaN(h)) {
|
||||
if (h > w) return 'heightFix';
|
||||
return 'widthFix';
|
||||
}
|
||||
return 'widthFix';
|
||||
}
|
||||
return 'aspectFill';
|
||||
}
|
||||
|
||||
// 提取并应用图片的特定宽高
|
||||
const getImageStyle = (message) => {
|
||||
if (message.imgWidth && message.imgHeight) {
|
||||
if (props.phone === 'mi') {
|
||||
const w = parseInt(message.imgWidth);
|
||||
const h = parseInt(message.imgHeight);
|
||||
if (!isNaN(w) && !isNaN(h)) {
|
||||
if (h > w) {
|
||||
return { width: '214rpx', height: '256rpx' };
|
||||
} else {
|
||||
return { width: '256rpx', height: '214rpx' };
|
||||
}
|
||||
}
|
||||
} else if (props.phone === 'vivo') {
|
||||
const w = parseInt(message.imgWidth);
|
||||
const h = parseInt(message.imgHeight);
|
||||
if (!isNaN(w) && !isNaN(h)) {
|
||||
if (h > w) {
|
||||
return { width: '284rpx', height: '400rpx' };
|
||||
} else {
|
||||
return { width: '400rpx', height: '284rpx' };
|
||||
}
|
||||
}
|
||||
} else if (props.phone === 'huawei') {
|
||||
const w = parseInt(message.imgWidth);
|
||||
const h = parseInt(message.imgHeight);
|
||||
if (!isNaN(w) && !isNaN(h)) {
|
||||
if (h > w) {
|
||||
return { height: '500rpx', 'max-height': '500rpx' };
|
||||
} else {
|
||||
return { width: '500rpx', 'max-width': '500rpx' };
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
width: message.imgWidth,
|
||||
height: message.imgHeight
|
||||
|
|
@ -550,7 +605,7 @@ const onSortTouchEnd = () => {
|
|||
background-color: transparent !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
border-radius: 16rpx !important;
|
||||
// border-radius: 16rpx !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -597,6 +652,85 @@ const onSortTouchEnd = () => {
|
|||
margin: 4rpx 30rpx 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.image-bubble {
|
||||
border-radius: 0 !important;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.image-wrap-left {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
left: 10px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
/* 使用径向渐变画出反向圆角:中心点在右下角,34rpx以内透明,以外纯白 */
|
||||
background: radial-gradient(circle at bottom right, transparent 16.5px, #FFFFFF 17px);
|
||||
}
|
||||
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
width: 10px;
|
||||
background-color: #FFFFFF !important;
|
||||
left: 0px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.image-wrap-right {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
/* 使用径向渐变画出反向圆角:中心点在右下角,34rpx以内透明,以外纯白 */
|
||||
background: radial-gradient(circle at bottom left, transparent 16.5px, #FFFFFF 17px);
|
||||
}
|
||||
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
width: 10px;
|
||||
background-color: #FFFFFF !important;
|
||||
right: 0px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mask-left-bottom {
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.mask-right-bottom {
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
bottom: 0px;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -615,6 +749,14 @@ const onSortTouchEnd = () => {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.image-tail-left {
|
||||
align-items: center;
|
||||
|
||||
::v-deep.image-bubble {
|
||||
margin-right: 24rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tail-left::after {
|
||||
position: absolute;
|
||||
left: 18rpx;
|
||||
|
|
@ -630,6 +772,23 @@ const onSortTouchEnd = () => {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.delivered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
position: relative;
|
||||
margin-bottom: 70rpx;
|
||||
|
||||
.send-text {
|
||||
margin-right: 32rpx;
|
||||
font-weight: 400;
|
||||
font-size: 20rpx;
|
||||
color: #8B8B8B;
|
||||
line-height: 20rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.delivered::before {
|
||||
position: absolute;
|
||||
right: 18rpx;
|
||||
|
|
@ -651,10 +810,28 @@ const onSortTouchEnd = () => {
|
|||
width: 28rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.chat-image {
|
||||
width: 516rpx;
|
||||
max-width: 516rpx !important;
|
||||
|
||||
}
|
||||
|
||||
.chat-image-left {
|
||||
border-radius: 0 34rpx 34rpx 0;
|
||||
}
|
||||
|
||||
.chat-image-right {
|
||||
border-radius: 34rpx 0 0 34rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 小米样式
|
||||
.mi-style {
|
||||
.chat-image {
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
.top-text {
|
||||
text-align: center;
|
||||
font-size: 20rpx;
|
||||
|
|
@ -888,6 +1065,10 @@ const onSortTouchEnd = () => {
|
|||
margin-top: 24rpx !important;
|
||||
}
|
||||
|
||||
.chat-image {
|
||||
border-radius: 32rpx;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #ACACAC;
|
||||
font-size: 24rpx;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,51 @@
|
|||
<template>
|
||||
<view class="preview-container" v-if="show">
|
||||
<view class="header" @tap.stop :style="{ 'padding-top': statusBarHeight }">
|
||||
<image class="icon-back" src="/static/image/phone-message/oppo/back-white.png" @tap="close"></image>
|
||||
<image class="icon-download" src="/static/image/phone-message/oppo/save-white.png"></image>
|
||||
</view>
|
||||
<!-- 图片显示 -->
|
||||
<swiper class="preview-swiper" :current="current" @change="onChange">
|
||||
<swiper-item v-for="(imgSrc, index) in images" :key="index">
|
||||
<image class="preview-img" :src="imgSrc" mode="aspectFit"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="preview-container" :class="phone === 'mi' ? 'mi-bg' : 'oppo-bg'" v-if="show">
|
||||
<template v-if="phone === 'oppo' || !phone || phone === 'iphone' || phone === 'huawei' || phone === 'vivo'">
|
||||
<view class="header" @tap.stop :style="{ 'padding-top': statusBarHeight }">
|
||||
<image class="icon-back" src="/static/image/phone-message/oppo/back-white.png" @tap="close"></image>
|
||||
<image class="icon-download" src="/static/image/phone-message/oppo/save-white.png"></image>
|
||||
</view>
|
||||
<!-- 图片显示 -->
|
||||
<swiper class="preview-swiper" :current="current" @change="onChange">
|
||||
<swiper-item v-for="(imgSrc, index) in images" :key="index">
|
||||
<image class="preview-img" :src="imgSrc" mode="aspectFit"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</template>
|
||||
|
||||
<template v-else-if="phone === 'mi'">
|
||||
<view class="mi-header" @tap.stop :style="{ 'padding-top': statusBarHeight }">
|
||||
<!-- 复用现有返回图标并通过滤镜反色 -->
|
||||
<image class="mi-icon-back" src="/static/image/phone-message/oppo/back-white.png" @tap="close"></image>
|
||||
<view class="mi-header-center">
|
||||
<view class="mi-date">{{ currentFormatDate }}</view>
|
||||
<view class="mi-time">{{ currentFormatTime }}</view>
|
||||
</view>
|
||||
<view class="mi-icon-right"></view> <!-- 占位符以居中 -->
|
||||
</view>
|
||||
<swiper class="preview-swiper mi-swiper" :current="current" @change="onChange">
|
||||
<swiper-item v-for="(imgSrc, index) in images" :key="index">
|
||||
<view class="mi-img-container">
|
||||
<image class="mi-preview-img" :src="imgSrc" mode="aspectFill"></image>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="mi-footer">
|
||||
<view class="mi-footer-item">
|
||||
<image class="mi-footer-icon" src="/static/image/phone-message/mi/baocun.png"></image>
|
||||
<view class="mi-footer-text">保存</view>
|
||||
</view>
|
||||
<view class="mi-footer-item">
|
||||
<view class="mi-more-icon">⋯</view>
|
||||
<view class="mi-footer-text">更多</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, computed } from 'vue';
|
||||
|
||||
const statusBarHeight = (uni.getSystemInfoSync().statusBarHeight || 44) + 'px';
|
||||
|
||||
|
|
@ -24,9 +55,17 @@ const props = defineProps({
|
|||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
times: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
phone: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['update:show']);
|
||||
|
|
@ -41,6 +80,53 @@ const onChange = (e) => {
|
|||
current.value = e.detail.current;
|
||||
};
|
||||
|
||||
const currentFormatDate = computed(() => {
|
||||
if (props.times && props.times.length > current.value) {
|
||||
const timeVal = props.times[current.value];
|
||||
if (timeVal) {
|
||||
if (typeof timeVal === 'string' && timeVal.includes('-')) {
|
||||
const parts = timeVal.split(' ');
|
||||
if (parts.length > 0) {
|
||||
const dateParts = parts[0].split('-');
|
||||
if (dateParts.length === 3) {
|
||||
return `${dateParts[0]}年${parseInt(dateParts[1])}月${parseInt(dateParts[2])}日`;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback
|
||||
const safeTimeStr = String(timeVal).replace(/-/g, '/');
|
||||
const d = new Date(safeTimeStr);
|
||||
if (!isNaN(d.getTime())) {
|
||||
return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const currentFormatTime = computed(() => {
|
||||
if (props.times && props.times.length > current.value) {
|
||||
const timeVal = props.times[current.value];
|
||||
if (timeVal) {
|
||||
if (typeof timeVal === 'string' && timeVal.includes(':')) {
|
||||
const parts = timeVal.split(' ');
|
||||
if (parts.length > 1) {
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
// Fallback
|
||||
const safeTimeStr = String(timeVal).replace(/-/g, '/');
|
||||
const d = new Date(safeTimeStr);
|
||||
if (!isNaN(d.getTime())) {
|
||||
const h = d.getHours().toString().padStart(2, '0');
|
||||
const m = d.getMinutes().toString().padStart(2, '0');
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
emit('update:show', false);
|
||||
};
|
||||
|
|
@ -75,12 +161,19 @@ const close = () => {
|
|||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #000000;
|
||||
z-index: 999999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.oppo-bg {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.mi-bg {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.header {
|
||||
box-sizing: content-box;
|
||||
width: 100%;
|
||||
|
|
@ -122,4 +215,113 @@ const close = () => {
|
|||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ================= 小米样式 ================= */
|
||||
.mi-header {
|
||||
box-sizing: content-box;
|
||||
width: 100%;
|
||||
height: 98rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.mi-icon-back {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin: 0 34rpx;
|
||||
filter: invert(1);
|
||||
/* 白变黑 */
|
||||
}
|
||||
|
||||
.mi-icon-right {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin: 0 34rpx;
|
||||
}
|
||||
|
||||
.mi-header-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mi-date {
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mi-time {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.mi-swiper {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mi-img-container {
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.mi-preview-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mi-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
padding-top: 20rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.mi-footer-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mi-footer-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.mi-more-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
line-height: 38rpx;
|
||||
font-size: 40rpx;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
margin-bottom: 8rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mi-footer-text {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -21,14 +21,25 @@
|
|||
<view class="title-box flex-between">
|
||||
<text class="title">{{ displayTitle(item.title) }}</text>
|
||||
<text class="time">{{ formatDate(getLastMessage(item.chatList)?.time || item.time)
|
||||
}}</text>
|
||||
}}</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view v-if="isImageMsg(getLastMessage(item.chatList))"
|
||||
class="flex flex-align-center">
|
||||
<image style="width: 24rpx;height: 24rpx;margin-right: 16rpx;"
|
||||
src="/static/image/phone-message/oppo/link.png"></image>
|
||||
<text>[图片]</text>
|
||||
<template v-if="phone === 'iphone'">
|
||||
<text>附件: 1张照片</text>
|
||||
</template>
|
||||
<template v-else-if="phone === 'huawei'">
|
||||
<text>您的好友给您发了一个图片</text>
|
||||
</template>
|
||||
<template v-else-if="phone == 'oppo'">
|
||||
<image style="width: 24rpx;height: 24rpx;margin-right: 16rpx;"
|
||||
src="/static/image/phone-message/oppo/link.png"></image>
|
||||
<text>[图片]</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text>[图片]</text>
|
||||
</template>
|
||||
</view>
|
||||
<rich-text v-else :nodes="getLastMessage(item.chatList)?.content || ''"></rich-text>
|
||||
</view>
|
||||
|
|
@ -37,7 +48,8 @@
|
|||
<image v-if="phone == 'iphone'" src="/static/image/phone-message/iphone/right.png">
|
||||
</image>
|
||||
<image v-if="item.noNotice && phone == 'iphone'" class="m-t-8"
|
||||
src="/static/image/phone-message/iphone/notice.png"></image>
|
||||
src="/static/image/phone-message/iphone/notice.png">
|
||||
</image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -133,20 +145,11 @@ const isImageMsg = (message) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取最新一条有效消息(非 oppo 屏蔽图片消息)
|
||||
* 获取最新一条有效消息
|
||||
*/
|
||||
const getLastMessage = (chatList) => {
|
||||
if (!chatList || chatList.length === 0) return null;
|
||||
if (props.phone === 'oppo') {
|
||||
return chatList[chatList.length - 1];
|
||||
}
|
||||
// 非 oppo 机型,倒序查找最后一条非图片消息
|
||||
for (let i = chatList.length - 1; i >= 0; i--) {
|
||||
if (!isImageMsg(chatList[i])) {
|
||||
return chatList[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return chatList[chatList.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
2
main.js
|
|
@ -29,7 +29,7 @@ export function createApp() {
|
|||
app.config.globalProperties.$system = plus.os.name;
|
||||
// #endif
|
||||
app.config.globalProperties.$systemInfo = systemInfo
|
||||
uni.setStorageSync('version', '1.0.6.sp1')
|
||||
uni.setStorageSync('version', '1.0.6.sp3')
|
||||
app.config.globalProperties.$version = uni.getStorageSync('version')
|
||||
app.use(globalMethods);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template>
|
||||
<!-- 水印 -->
|
||||
<view v-if="$isVip()">
|
||||
<watermark dark="light" source="uni_alipay_other_message" />
|
||||
|
|
@ -88,7 +88,14 @@
|
|||
<view class="edit-row">
|
||||
<text class="edit-label">内容:</text>
|
||||
</view>
|
||||
<editor id="editor" class="edit-textarea" placeholder="请输入消息内容..." @ready="onEditorReady">
|
||||
<template v-if="editingMessage && editingMessage.type === 'image'">
|
||||
<view class="edit-image-replace-box" @tap="replaceEditImage">
|
||||
<image :src="editingNewImg || editingMessage.imgUrl" mode="aspectFit" class="replace-img">
|
||||
</image>
|
||||
<view class="replace-tip">点击替换图片</view>
|
||||
</view>
|
||||
</template>
|
||||
<editor v-else id="editor" class="edit-textarea" placeholder="请输入消息内容..." @ready="onEditorReady">
|
||||
</editor>
|
||||
</view>
|
||||
<view class="edit-footer">
|
||||
|
|
@ -161,7 +168,8 @@
|
|||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<ImagePreview v-model:show="showPreview" :images="previewImages" :currentIndex="previewIndex" />
|
||||
<ImagePreview v-model:show="showPreview" :images="previewImages" :times="previewTimes"
|
||||
:currentIndex="previewIndex" :phone="data.phone" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -199,13 +207,26 @@ const selectedMessage = ref(null)
|
|||
|
||||
const showEditPopup = ref(false)
|
||||
const editingMessage = ref(null)
|
||||
const editingNewImg = ref("")
|
||||
const replaceEditImage = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
|
||||
editingNewImg.value = res.tempFilePaths[0];
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const showPreview = ref(false)
|
||||
const previewImages = ref([])
|
||||
const previewTimes = ref([])
|
||||
const previewIndex = ref(0)
|
||||
|
||||
const handlePreviewImage = (data) => {
|
||||
previewImages.value = data.images
|
||||
previewIndex.value = data.index
|
||||
previewTimes.value = data.times || []
|
||||
showPreview.value = true
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +272,7 @@ const saveChatList = () => {
|
|||
const onEditorReady = () => {
|
||||
uni.createSelectorQuery().select('#editor').context((res) => {
|
||||
editorCtx = res.context
|
||||
if (editingMessage.value && editingMessage.value.content) {
|
||||
if (editingMessage.value && editingMessage.value.content && editingMessage.value.type !== 'image') {
|
||||
editorCtx.setContents({
|
||||
html: editingMessage.value.content
|
||||
})
|
||||
|
|
@ -296,6 +317,7 @@ const closeActionPopup = () => {
|
|||
*/
|
||||
const handleEdit = () => {
|
||||
editingMessage.value = selectedMessage.value;
|
||||
editingNewImg.value = "";
|
||||
editingTime.value = selectedMessage.value.time || "";
|
||||
// 拆分日期和时刻部分
|
||||
const parts = (selectedMessage.value.time || "").split(' ')
|
||||
|
|
@ -308,7 +330,7 @@ const handleEdit = () => {
|
|||
closeActionPopup();
|
||||
|
||||
// 如果已经初始化过了直接赋值
|
||||
if (editorCtx) {
|
||||
if (editorCtx && editingMessage.value.type !== 'image') {
|
||||
setTimeout(() => {
|
||||
editorCtx.setContents({
|
||||
html: editingMessage.value.content
|
||||
|
|
@ -320,6 +342,7 @@ const handleEdit = () => {
|
|||
const closeEditPopup = () => {
|
||||
showEditPopup.value = false;
|
||||
editingMessage.value = null;
|
||||
editingNewImg.value = "";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -346,32 +369,121 @@ const onSimKaChange = (value) => {
|
|||
editingSimKa.value = value
|
||||
}
|
||||
|
||||
const confirmEdit = () => {
|
||||
if (editingMessage.value && editorCtx) {
|
||||
editorCtx.getContents({
|
||||
success: (res) => {
|
||||
const index = messageList.value.findIndex(item => item.id === editingMessage.value.id)
|
||||
if (index > -1) {
|
||||
messageList.value[index].content = res.html;
|
||||
messageList.value[index].time = editingTime.value;
|
||||
// 保存 timeMode: 'auto'|'show'|'hide'
|
||||
if (editingTimeMode.value === 'auto') {
|
||||
delete messageList.value[index].timeMode;
|
||||
} else {
|
||||
messageList.value[index].timeMode = editingTimeMode.value;
|
||||
}
|
||||
// 兼容旧 hideTime 字段:一并清除
|
||||
delete messageList.value[index].hideTime;
|
||||
if (editingSimKa.value) {
|
||||
messageList.value[index].simIndex = Number(editingSimKa.value);
|
||||
} else {
|
||||
delete messageList.value[index].simIndex;
|
||||
const confirmEdit = async () => {
|
||||
if (editingMessage.value) {
|
||||
const index = messageList.value.findIndex(item => item.id === editingMessage.value.id)
|
||||
if (index > -1) {
|
||||
if (editingMessage.value.type === 'image') {
|
||||
let finalImgPath = editingMessage.value.imgUrl;
|
||||
let oldImgUrl = '';
|
||||
|
||||
if (editingNewImg.value) {
|
||||
try {
|
||||
// #ifdef APP-PLUS
|
||||
const saveRes = await new Promise((resolve, reject) => {
|
||||
uni.saveFile({
|
||||
tempFilePath: editingNewImg.value,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
finalImgPath = saveRes.savedFilePath;
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
finalImgPath = editingNewImg.value;
|
||||
// #endif
|
||||
|
||||
oldImgUrl = messageList.value[index].imgUrl;
|
||||
|
||||
const imgInfo = await new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src: finalImgPath,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
let w = imgInfo.width;
|
||||
let h = imgInfo.height;
|
||||
let width = '100%';
|
||||
let height = 'auto';
|
||||
if (w && h) {
|
||||
if (w > h) {
|
||||
let scale = w / 200;
|
||||
width = '200px';
|
||||
height = (h / scale) + 'px';
|
||||
} else {
|
||||
let scale = h / 200;
|
||||
height = '200px';
|
||||
width = (w / scale) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
const newHtml = `<img src="${finalImgPath}" style="width: ${width}; height: ${height}; border-radius: 16rpx; display: block;" />`;
|
||||
messageList.value[index].content = newHtml;
|
||||
messageList.value[index].imgUrl = finalImgPath;
|
||||
messageList.value[index].imgWidth = width;
|
||||
messageList.value[index].imgHeight = height;
|
||||
} catch (e) {
|
||||
console.error('保存替换图片失败', e);
|
||||
uni.showToast({ title: '保存图片失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
messageList.value[index].time = editingTime.value;
|
||||
if (editingTimeMode.value === 'auto') {
|
||||
delete messageList.value[index].timeMode;
|
||||
} else {
|
||||
messageList.value[index].timeMode = editingTimeMode.value;
|
||||
}
|
||||
delete messageList.value[index].hideTime;
|
||||
if (editingSimKa.value) {
|
||||
messageList.value[index].simIndex = Number(editingSimKa.value);
|
||||
} else {
|
||||
delete messageList.value[index].simIndex;
|
||||
}
|
||||
|
||||
closeEditPopup();
|
||||
saveChatList();
|
||||
|
||||
if (oldImgUrl && oldImgUrl.includes('_doc/')) {
|
||||
// #ifdef APP-PLUS
|
||||
uni.removeSavedFile({
|
||||
filePath: oldImgUrl,
|
||||
success: () => console.log('替换旧图片,删除成功:', oldImgUrl),
|
||||
fail: (err) => console.warn('替换旧图片,删除失败:', oldImgUrl, err)
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
})
|
||||
|
||||
if (editorCtx) {
|
||||
editorCtx.getContents({
|
||||
success: (res) => {
|
||||
messageList.value[index].content = res.html;
|
||||
messageList.value[index].time = editingTime.value;
|
||||
// 保存 timeMode: 'auto'|'show'|'hide'
|
||||
if (editingTimeMode.value === 'auto') {
|
||||
delete messageList.value[index].timeMode;
|
||||
} else {
|
||||
messageList.value[index].timeMode = editingTimeMode.value;
|
||||
}
|
||||
// 兼容旧 hideTime 字段:一并清除
|
||||
delete messageList.value[index].hideTime;
|
||||
if (editingSimKa.value) {
|
||||
messageList.value[index].simIndex = Number(editingSimKa.value);
|
||||
} else {
|
||||
delete messageList.value[index].simIndex;
|
||||
}
|
||||
closeEditPopup();
|
||||
saveChatList();
|
||||
}
|
||||
})
|
||||
} else {
|
||||
closeEditPopup();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
closeEditPopup();
|
||||
}
|
||||
|
|
@ -1052,4 +1164,28 @@ const confirmAdd = async () => {
|
|||
padding-bottom: 2rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.edit-image-replace-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
min-height: 200rpx;
|
||||
}
|
||||
|
||||
.replace-img {
|
||||
max-width: 100%;
|
||||
max-height: 300rpx;
|
||||
}
|
||||
|
||||
.replace-tip {
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #007AFF;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 534 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1016 B |
|
After Width: | Height: | Size: 974 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 309 KiB After Width: | Height: | Size: 101 KiB |