Merge branch 'Branch_1' of https://git.u8t.cn/tangxinyue/alipay-emulator into Branch_1

This commit is contained in:
小李 2026-05-27 17:55:19 +08:00
commit b0af3d1d5e
23 changed files with 1908 additions and 250 deletions

View File

@ -4,18 +4,19 @@
<!-- <view class="top-placeholder" :style="{ height: data.topBoxHeight + 'px' }"></view> -->
<view class="top-box " :style="{ top: data.statusBarHeight + 'px' }">
<slot name="top">
<view class="top-container" v-show="!sortMode">
<view class="top-container" v-show="!sortMode" @click.stop="handleTitleClick">
<view class="h100 flex-align-center flex-justify-between">
<view class="left flex-align-center">
<image @click="util.goBack()" :src="`/static/image/phone-message/${phone}/back.png`">
<image @click.stop="util.goBack()" :src="`/static/image/phone-message/${phone}/back.png`">
</image>
<view v-if="phone == 'iphone' && number > 0" class="number-box">{{ number }}</view>
</view>
<view class="center">
<image v-if="phone == 'iphone' || phone == 'huawei'" class="img shrink-0"
:class="chatInfo.imgShape"
:src="chatInfo.img || `/static/image/phone-message/${phone}/default.png`">
</image>
<text v-if="phone != 'iphone'" class="title">{{ chatInfo.title }}</text>
<text v-if="phone != 'iphone'" class="title">{{ displayTitle }}</text>
<text v-if="phone == 'oppo' && chatInfo.area" class="second-text">{{ chatInfo.area }}</text>
</view>
<view class="right flex-align-center">
@ -37,7 +38,7 @@
</view>
</view>
<view v-if="phone == 'iphone'" class="text-box flex-align-center flex-justify-center">
<text class="title">{{ chatInfo.title }}</text>
<text class="title">{{ displayTitle }}</text>
<!-- <uni-icons type="right" size="10" color="#D8D8D8"></uni-icons> -->
<image style="width: 16rpx;height: 16rpx;"
src="/static/image/phone-message/iphone/chat-right.png"></image>
@ -56,7 +57,8 @@
<slot name="bottom">
<view class="bottom-container flex-align-center" v-show="!sortMode">
<image v-if="phone != 'huawei' && phone != 'vivo'" class="add-img shrink-0"
:src="`/static/image/phone-message/${phone}/chat-left.png`"></image>
:src="`/static/image/phone-message/${phone}/chat-left.png`"
@click="phone === 'oppo' ? chooseImage() : null"></image>
<image v-if="phone == 'huawei'" class="add-img shrink-0"
:src="`/static/image/phone-message/huawei/emoji.png`"></image>
<view class="search-box flex-1 flex-align-center">
@ -66,10 +68,11 @@
src="/static/image/phone-message/huawei/down.png"></image>
<!-- <input class="input flex-1" :placeholder="showInfo.placeholder" v-model="content"
@input="onInput"></input> -->
<textarea class="input flex-1" :adjust-position="false" fixed auto-height show-confirm-bar
<!-- <textarea class="input flex-1" :adjust-position="false" fixed auto-height show-confirm-bar
auto-blur :placeholder="showInfo.placeholder" v-model="content" @input="onInput"
@focus="onFocus" @blur="onBlur"></textarea>
<!-- <editor class="input flex-1" :placeholder="showInfo.placeholder"></editor> -->
@focus="onFocus" @blur="onBlur"></textarea> -->
<editor id="chat-layout-editor" class="input flex-1" :placeholder="showInfo.placeholder"
@input="onInput"></editor>
<image v-if="phone == 'iphone' && !isSend" class="right-icon"
src="/static/image/phone-message/iphone/mic.png"></image>
<image v-if="phone == 'iphone' && isSend" class="right-send-icon"
@ -100,14 +103,13 @@
</view>
</slot>
</view>
<view class="bottom-placeholder" :style="{ height: (data.bottomBoxHeight + data.keyboardHeight) + 'px' }">
<view class="bottom-placeholder" :style="{ height: (data.bottomBoxHeight) + 'px' }">
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, computed, toRefs } from 'vue'
import { onLoad, onPageScroll } from "@dcloudio/uni-app";
import { stringUtil, dateUtil, util } from '@/utils/common.js';
import { reactive, onMounted, computed, toRefs } from 'vue'
import { dateUtil, util } from '@/utils/common.js';
const props = defineProps({
//
phone: {
@ -116,14 +118,14 @@ const props = defineProps({
},
chatInfo: {
type: Object,
default: {
default: () => ({
unRead: false,
noNotice: false,
img: "",
title: "淘宝通知",
content: "您关注的商品降价啦,快来抢购吧!",
time: "2026-02-20 20:55:12"
}
})
},
//
sortMode: {
@ -148,23 +150,16 @@ const data = reactive({
lastClickTime: 0
})
let { isSend, content, simIndex, scrollTop, lastClickTime } = toRefs(data)
const emit = defineEmits(['send', 'dblclick-left', 'dblclick-right'])
onMounted(() => {
// DOM
setTimeout(() => {
data.scrollTop = 99999 + Math.random();
}, 100);
})
let { isSend, content, simIndex, scrollTop } = toRefs(data)
const emit = defineEmits(['send', 'dblclick-left', 'dblclick-right', 'title-click'])
const onInput = (e) => {
content.value = e.detail.value
if (content.value.length > 0) {
isSend.value = true
} else {
content.value = e.detail.html
const htmlStr = (content.value || '').trim()
if (htmlStr === '' || htmlStr === '<p><br></p>' || htmlStr === '<br>') {
isSend.value = false
} else {
isSend.value = true
}
setTimeout(() => {
uni.createSelectorQuery().select('.bottom-box').boundingClientRect(rect => {
@ -175,19 +170,7 @@ const onInput = (e) => {
}, 50);
}
const onFocus = (e) => {
if (e.detail.height) {
data.keyboardHeight = e.detail.height;
//
setTimeout(() => {
data.scrollTop = 99999 + Math.random();
}, 100);
}
}
const onBlur = () => {
data.keyboardHeight = 0;
}
/**
* 切换sim卡
@ -212,6 +195,14 @@ const sendMessage = () => {
data.content = ""
data.isSend = false
// editor
uni.createSelectorQuery().select('#chat-layout-editor').context((res) => {
if (res && res.context) {
res.context.clear()
}
data.isSend = false
}).exec()
// DOM
setTimeout(() => {
data.scrollTop = 99999 + Math.random();
@ -220,7 +211,9 @@ const sendMessage = () => {
data.bottomBoxHeight = rect.height;
}
}).exec();
data.isSend = false
}, 100);
data.isSend = false
}
/**
@ -255,6 +248,23 @@ const handleBoxClick = (e) => {
}
const handleTitleClick = () => {
emit('title-click');
}
//
const displayTitle = computed(() => {
let title = props.chatInfo.title || '';
if (props.phone === 'oppo') {
const noSpace = title.replace(/\s+/g, '');
if (/^\d{11}$/.test(noSpace)) {
return noSpace.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
}
}
return title;
})
//
const showInfo = computed(() => {
let placeholder
@ -280,6 +290,84 @@ const showInfo = computed(() => {
return { placeholder }
})
/**
* 选择图片并发送
*/
const chooseImage = () => {
uni.chooseImage({
count: 1, //
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success: (res) => {
const tempFilePaths = res.tempFilePaths;
if (tempFilePaths.length > 0) {
const tempPath = tempFilePaths[0];
const processImage = (finalPath) => {
//
uni.getImageInfo({
src: finalPath,
success: (image) => {
let w = image.width;
let h = image.height;
// EXIF
if (image.orientation && ['left', 'right', 'left-mirrored', 'right-mirrored'].includes(image.orientation)) {
w = image.height;
h = image.width;
}
const isHorizontal = w >= h;
const width = isHorizontal ? '368rpx' : '274rpx';
const height = isHorizontal ? '274rpx' : '368rpx';
const date = dateUtil.now("YYYY-MM-DD HH:mm")
// HTMLimg
const content = `<img src="${finalPath}" style="width: ${width}; height: ${height}; border-radius: 16rpx; display: block;" />`;
const params = {
content: content,
imgUrl: finalPath,
type: 'image',
imgWidth: width,
imgHeight: height,
simIndex: simIndex.value,
time: date,
isMe: true
}
emit('send', params)
//
setTimeout(() => {
data.scrollTop = 99999 + Math.random();
uni.createSelectorQuery().select('.bottom-box').boundingClientRect(rect => {
if (rect) {
data.bottomBoxHeight = rect.height;
}
}).exec();
}, 100);
}
})
};
// #ifdef APP-PLUS
uni.saveFile({
tempFilePath: tempPath,
success: (saveRes) => {
processImage(saveRes.savedFilePath);
},
fail: () => {
processImage(tempPath);
}
});
// #endif
// #ifndef APP-PLUS
processImage(tempPath);
// #endif
}
}
})
}
onMounted(() => {
//
const systemInfo = uni.getSystemInfoSync();
@ -299,6 +387,16 @@ onMounted(() => {
}).exec();
}, 50);
// DOM
setTimeout(() => {
data.scrollTop = 99999 + Math.random();
uni.createSelectorQuery().select('.bottom-box').boundingClientRect(rect => {
if (rect) {
data.bottomBoxHeight = rect.height;
}
}).exec();
}, 300);
})
@ -325,7 +423,7 @@ onMounted(() => {
.center-box {
overflow: hidden;
// overflow-y: scroll;
padding-bottom: 12px;
// padding-bottom: 12px;
}
.fixed-bottom-box {
@ -347,6 +445,13 @@ onMounted(() => {
//
.iphone-style {
.circle {
border-radius: 50% !important;
}
.square {
border-radius: 16rpx !important;
}
.status-placeholder {
background-color: #F7F7F7;
@ -411,7 +516,6 @@ onMounted(() => {
left: 20rpx;
margin-right: 6rpx;
font-weight: 500;
line-height: 20rpx;
}
}
}
@ -628,11 +732,25 @@ onMounted(() => {
.top-box {
.top-container::after {
position: absolute;
content: '';
width: 100%;
height: 1px;
bottom: 0;
left: 0;
right: 0;
background-color: #D0D1D3;
transform: scaleY(0.3);
}
.top-container {
position: relative;
padding: 0 52rpx;
background-color: #F0F1F3;
height: 88rpx;
box-shadow: inset 0 -0.3px 0 0 #D0D1D3;
height: 98rpx;
padding-top: 6rpx;
padding-bottom: 12rpx;
.left {
@ -752,6 +870,7 @@ onMounted(() => {
max-height: 160rpx;
overflow: hidden;
overflow-y: scroll;
caret-color: #1ABA11;
}
.right-icon {
@ -821,7 +940,7 @@ onMounted(() => {
font-size: 40rpx;
color: #1A1A1A;
font-weight: 500;
line-height: 36rpx;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -1057,4 +1176,9 @@ onMounted(() => {
}
}
.bottom-placeholder {
margin-top: env(safe-area-inset-bottom);
margin-top: constant(safe-area-inset-bottom);
}
</style>

View File

@ -10,8 +10,8 @@
}">
<!-- 排序模式下的拖拽手柄 -->
<view v-if="sortMode" class="sort-handle-wrap" @longpress="onSortLongPress(index, $event)"
@touchmove.stop.prevent="onSortTouchMove(index, $event)" @touchend.stop="onSortTouchEnd(index, $event)">
<view v-if="sortMode" class="sort-handle-wrap" @longpress="onSortLongPress(index)"
@touchmove.stop.prevent="onSortTouchMove($event)" @touchend.stop="onSortTouchEnd()">
<view class="sort-handle-bar"></view>
<view class="sort-handle-bar"></view>
<view class="sort-handle-bar"></view>
@ -34,13 +34,15 @@
</image>
</view>
<view class="chat-box" :id="'msg-' + index" :class="{
'tail-right': shouldApplyTailRight(index),
'tail-left': shouldApplyTailLeft(index),
'tail-right': shouldApplyTailRight(index) && !isImageMsg(message),
'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">
<view v-html="formatMessageContent(message.content, message.isMe)"></view>
<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)"></image>
<rich-text v-else :nodes="formatMessageContent(message.content, message.isMe)"></rich-text>
</view>
</view>
<view v-if="phone == 'huawei'" class="second-info">
@ -56,9 +58,8 @@
</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';
import { ref, onMounted, computed, watch, nextTick } from 'vue'
import { util } from '@/utils/common.js';
const props = defineProps({
//
phone: {
@ -67,7 +68,7 @@ const props = defineProps({
},
messageList: {
type: Array,
default: []
default: () => []
},
//
sortMode: {
@ -105,7 +106,11 @@ let sortItemRects = []
// sortMode
watch(() => props.sortMode, (val) => {
if (val) {
localSortList.value = props.messageList.map(item => ({ ...item }))
let list = props.messageList;
if (props.phone !== 'oppo') {
list = list.filter(msg => !isImageMsg(msg));
}
localSortList.value = list.map(item => ({ ...item }))
} else {
dragIndex.value = -1
dragOverIndex.value = -1
@ -115,10 +120,15 @@ watch(() => props.sortMode, (val) => {
}
})
//
// oppo
const displayList = computed(() => {
if (props.sortMode) return localSortList.value
return props.messageList
let list = props.messageList;
if (props.phone !== 'oppo') {
list = list.filter(msg => !isImageMsg(msg));
}
return list;
})
// isMe==trueisMe==truem-t-16
@ -132,18 +142,7 @@ const shouldApplyMt16 = (index) => {
return false;
}
// isMe==trueisMe==truem-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) => {
@ -335,11 +334,45 @@ const formatMessageContent = (content, isMe) => {
let color = '#3A85F8'
if (props.phone == 'iphone') color = '#0B7AE3'
if (props.phone == 'mi') color = '#3A85F8'
if (props.phone == 'oppo') color = '#03A311'
const colorStyle = !isMe ? `color: ${color};` : '';
return `<span style="text-decoration: underline; ${colorStyle}">${match}</span>`;
});
}
//
const isImageMsg = (message) => {
if (message.type === 'image') return true;
if (message.content && typeof message.content === 'string') {
const str = message.content.trim();
// <img
if (str.startsWith('<img') && str.endsWith('/>') && str.indexOf('<img') === str.lastIndexOf('<img')) {
return true;
}
}
return false;
}
// src
const getImageSrc = (message) => {
if (message.imgUrl) return message.imgUrl;
if (message.content && typeof message.content === 'string') {
const match = message.content.match(/src="([^"]+)"/);
if (match && match[1]) return match[1];
}
return '';
}
//
const getImageStyle = (message) => {
if (message.imgWidth && message.imgHeight) {
return {
width: message.imgWidth,
height: message.imgHeight
};
}
return {};
}
/**
* 长按信息
@ -373,7 +406,7 @@ const refreshSortRects = (callback) => {
/**
* 长按手柄开始拖拽
*/
const onSortLongPress = (idx, e) => {
const onSortLongPress = (idx) => {
dragIndex.value = idx
dragOverIndex.value = idx
isDragging = true
@ -386,7 +419,7 @@ const onSortLongPress = (idx, e) => {
/**
* 拖拽移动 - 根据触摸点 Y 坐标確定悬停位置和插入方向
*/
const onSortTouchMove = (idx, e) => {
const onSortTouchMove = (e) => {
if (!isDragging || dragIndex.value === -1) return
if (!e.touches || !e.touches[0]) return
const touchY = e.touches[0].clientY
@ -408,7 +441,7 @@ const onSortTouchMove = (idx, e) => {
/**
* 拖拽结束 - 根据 dropPosition 決定插入到目标的上方还是下方
*/
const onSortTouchEnd = (idx, e) => {
const onSortTouchEnd = () => {
if (!isDragging) return
const from = dragIndex.value
const to = dragOverIndex.value
@ -483,6 +516,21 @@ const onSortTouchEnd = (idx, e) => {
padding-bottom: 4rpx;
}
/* 纯图片消息覆盖样式 */
.chat-bubble.image-bubble {
background-color: transparent !important;
padding: 0 !important;
border: none !important;
border-radius: 16rpx !important;
overflow: hidden;
}
.chat-image {
max-width: 400rpx;
border-radius: 16rpx;
display: block;
}
//
.iphone-style {
.m-t-16 {

View File

@ -2,7 +2,7 @@
<view :style="`${phone}-style`">
<uni-swipe-action class="swipe-action">
<!-- 使用插槽 请自行给定插槽内容宽度-->
<uni-swipe-action-item class="swipe-action-item" v-for="(item, index) in list" :key="item.id">
<uni-swipe-action-item class="swipe-action-item" v-for="item in list" :key="item.id">
<view class="flex flex-align-center " @click="clickItem(item)">
<view class="item flex w100">
@ -19,13 +19,19 @@
<view class="border-box m-l-24 flex-1 flex flex-align-start">
<view class="main-box flex-1">
<view class="title-box flex-between">
<text class="title">{{ item.title }}</text>
<text class="time">{{ formatDate(item.chatList?.[item.chatList?.length -
1]?.time || item.time)
<text class="title">{{ displayTitle(item.title) }}</text>
<text class="time">{{ formatDate(getLastMessage(item.chatList)?.time || item.time)
}}</text>
</view>
<view class="content"
v-html="item.chatList?.[item.chatList?.length - 1]?.content || ''"></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>
</view>
<rich-text v-else :nodes="getLastMessage(item.chatList)?.content || ''"></rich-text>
</view>
</view>
<view class="box-right h100 flex-column flex-align-center">
<image v-if="phone == 'iphone'" src="/static/image/phone-message/iphone/right.png">
@ -56,18 +62,8 @@
</uni-swipe-action>
</view>
</template>
<script>
import { stringUtil } from '@/utils/common.js';
</script>
<script setup>
import {
ref,
reactive,
computed
} from 'vue'
import {
util,
dateUtil
} from '@/utils/common.js';
//
@ -107,6 +103,52 @@ const formatDate = (date) => {
}
}
/**
* 格式化联系人名称处理 OPPO 纯数字11位手机号显示
*/
const displayTitle = (title) => {
let t = title || '';
if (props.phone === 'oppo') {
const noSpace = t.replace(/\s+/g, '');
if (/^\d{11}$/.test(noSpace)) {
return noSpace.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
}
}
return t;
}
/**
* 判断该消息是否为图片消息
*/
const isImageMsg = (message) => {
if (!message) return false;
if (message.type === 'image') return true;
if (message.content && typeof message.content === 'string') {
const str = message.content.trim();
if (str.startsWith('<img') && str.endsWith('/>') && str.indexOf('<img') === str.lastIndexOf('<img')) {
return true;
}
}
return false;
}
/**
* 获取最新一条有效消息 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;
}
/**
* 点击列表元素
*/
@ -449,11 +491,24 @@ const editItem = (item) => {
.border-box {
position: relative;
padding: 4rpx 34rpx 28rpx 0;
padding: 4rpx 0 28rpx 0;
margin-left: 32rpx;
margin-right: 34rpx;
height: 100%;
}
.border-box::after {
position: absolute;
content: '';
width: 100%;
height: 1px;
bottom: 0;
left: 0;
right: 0;
background-color: #E2E2E2;
transform: scaleY(0.3);
}
.main-box {
.title-box {
margin-bottom: 16rpx;

View File

@ -38,7 +38,10 @@
"path": "chat-page/chat-page",
"style": {
"navigationBarTitleText": "短信聊天页面",
"navigationStyle": "custom"
"navigationStyle": "custom",
"app-plus": {
"softinputMode": "adjustResize"
}
}
}
]
@ -116,10 +119,23 @@
}
},
{
"path" : "taobao/add-shangou-order/add-shangou-order",
"style" :
{
"navigationBarTitleText" : "添加淘宝闪购订单详情",
"path": "taobao/add-shangou-order/add-shangou-order",
"style": {
"navigationBarTitleText": "添加淘宝闪购订单详情",
"navigationStyle": "custom"
}
},
{
"path": "pdd/list-index",
"style": {
"navigationBarTitleText": "拼多多购物列表",
"navigationStyle": "custom"
}
},
{
"path": "pdd/order-detail/order-detail",
"style": {
"navigationBarTitleText": "拼多多订单详情",
"navigationStyle": "custom"
}
}
@ -352,10 +368,9 @@
}
},
{
"path" : "about-this-iphone/about-this-iphone",
"style" :
{
"navigationBarTitleText" : "苹果关于本机",
"path": "about-this-iphone/about-this-iphone",
"style": {
"navigationBarTitleText": "苹果关于本机",
"navigationStyle": "custom"
}
}

View File

@ -8,97 +8,96 @@
</view>
<view :class="`${data.phone}-style`">
<ChatLayout :phone="data.phone" :chatInfo="data.data" :sortMode="isSortMode" @send="handleSend"
:number="data.number" @dblclick-left="onDblclickLeft" @dblclick-right="onDblclickRight">
<!-- 弹出操作层及遮罩 -->
<view v-if="showActionPopup" class="action-mask" @tap="closeActionPopup">
<view class="action-popup" :style="{ top: popupTop + 'px', left: popupLeft + 'px' }">
<view class="action-item" @tap.stop="handleEdit">
<image class="action-icon" src="/static/image/phone-message/bianji.png"></image>
<text>编辑</text>
</view>
<view class="action-item" @tap.stop="handleSwap">
<image class="action-icon" src="/static/image/phone-message/huhuan.png"></image>
<text>消息互换</text>
</view>
<view class="action-item" @tap.stop="handleSort">
<image class="action-icon" src="/static/image/phone-message/sort.png"></image>
<text>排序</text>
</view>
<view class="action-item" @tap.stop="handleChangeSpeaker">
<image class="action-icon" src="/static/image/phone-message/change.png"></image>
<text>切换发言人</text>
</view>
<view class="action-item" @tap.stop="handleDelete">
<image class="action-icon" src="/static/image/phone-message/shanchu.png"></image>
<text>删除</text>
</view>
<!-- 向上指的三角形因为要求在长按元素下方 -->
<view class="triangle"></view>
</view>
</view>
<!-- 编辑消息弹窗 -->
<view v-if="showEditPopup" class="edit-mask" @tap="closeEditPopup">
<view class="edit-popup" @tap.stop>
<view class="edit-header">编辑消息</view>
<view class="edit-body">
<view class="edit-row">
<text class="edit-label">时间</text>
<view class="time-picker-group">
<picker mode="date" :fields="$system == 'Android' ? 'day' : ''" :value="editingDate"
@change="onDateChange">
<view class="time-picker-item">
<text>{{ editingDate || '选择日期' }}</text>
</view>
</picker>
<picker mode="time" :fields="$system == 'Android' ? 'minute' : ''"
:value="editingTimeOfDay" @change="onTimeOfDayChange">
<view class="time-picker-item">
<text>{{ editingTimeOfDay || '选择时刻' }}</text>
</view>
</picker>
</view>
</view>
<view class="edit-row" style="align-items: flex-start;">
<text class="edit-label" style="padding-top: 6rpx;">时间显示</text>
<view class="time-mode-group">
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'auto' }"
@tap="editingTimeMode = 'auto'">自动</view>
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'show' }"
@tap="editingTimeMode = 'show'">强制显示</view>
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'hide' }"
@tap="editingTimeMode = 'hide'">强制隐藏</view>
</view>
</view>
<view class="edit-row"
v-if="data.phone == 'huawei' || data.phone == 'oppo' || data.phone == 'vivo'">
<text class="edit-label">SIM卡</text>
<view class="edit-input"
style="padding: 0; background: transparent; display: flex; align-items: center; border-radius: 8rpx; height: 70rpx;">
<uni-data-select v-model="editingSimKa" :localdata="simList" :clear="false"
placeholder="请选择卡号" @change="onSimKaChange"
style="flex: 1; border: none !important; width: 100%;"></uni-data-select>
</view>
</view>
<view class="edit-row">
<text class="edit-label">内容</text>
</view>
<editor id="editor" class="edit-textarea" placeholder="请输入消息内容..." @ready="onEditorReady">
</editor>
</view>
<view class="edit-footer">
<view class="edit-btn cancel" @tap="closeEditPopup">取消</view>
<view class="edit-btn confirm" @tap="confirmEdit">确定</view>
</view>
</view>
</view>
:number="data.number" @dblclick-left="onDblclickLeft" @dblclick-right="onDblclickRight"
@title-click="handleTitleClick">
<ChatList :messageList="messageList" :phone="data.phone" :sortMode="isSortMode"
@onLongPress="onMessageLongPress" @sort="onSortChange"></ChatList>
</ChatLayout>
<!-- 弹出操作层及遮罩 -->
<view v-if="showActionPopup" class="action-mask" @tap="closeActionPopup">
<view class="action-popup" :style="{ top: popupTop + 'px', left: popupLeft + 'px' }">
<view class="action-item" @tap.stop="handleEdit">
<image class="action-icon" src="/static/image/phone-message/bianji.png"></image>
<text>编辑</text>
</view>
<view class="action-item" @tap.stop="handleSwap">
<image class="action-icon" src="/static/image/phone-message/huhuan.png"></image>
<text>消息互换</text>
</view>
<view class="action-item" @tap.stop="handleSort">
<image class="action-icon" src="/static/image/phone-message/sort.png"></image>
<text>排序</text>
</view>
<view class="action-item" @tap.stop="handleChangeSpeaker">
<image class="action-icon" src="/static/image/phone-message/change.png"></image>
<text>切换发言人</text>
</view>
<view class="action-item" @tap.stop="handleDelete">
<image class="action-icon" src="/static/image/phone-message/shanchu.png"></image>
<text>删除</text>
</view>
<!-- 向上指的三角形因为要求在长按元素下方 -->
<view class="triangle"></view>
</view>
</view>
<!-- 编辑消息弹窗 -->
<view v-if="showEditPopup" class="edit-mask" @tap="closeEditPopup">
<view class="edit-popup" @tap.stop>
<view class="edit-header">编辑消息</view>
<view class="edit-body">
<view class="edit-row">
<text class="edit-label">时间</text>
<view class="time-picker-group">
<picker mode="date" :fields="$system == 'Android' ? 'day' : ''" :value="editingDate"
@change="onDateChange">
<view class="time-picker-item">
<text>{{ editingDate || '选择日期' }}</text>
</view>
</picker>
<picker mode="time" :fields="$system == 'Android' ? 'minute' : ''" :value="editingTimeOfDay"
@change="onTimeOfDayChange">
<view class="time-picker-item">
<text>{{ editingTimeOfDay || '选择时刻' }}</text>
</view>
</picker>
</view>
</view>
<view class="edit-row" style="align-items: flex-start;">
<text class="edit-label" style="padding-top: 6rpx;">时间显示</text>
<view class="time-mode-group">
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'auto' }"
@tap="editingTimeMode = 'auto'">自动</view>
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'show' }"
@tap="editingTimeMode = 'show'">强制显示</view>
<view class="time-mode-btn" :class="{ active: editingTimeMode === 'hide' }"
@tap="editingTimeMode = 'hide'">强制隐藏</view>
</view>
</view>
<view class="edit-row"
v-if="data.phone == 'huawei' || data.phone == 'oppo' || data.phone == 'vivo'">
<text class="edit-label">SIM卡</text>
<view class="edit-input"
style="padding: 0; background: transparent; display: flex; align-items: center; border-radius: 8rpx; height: 70rpx;">
<uni-data-select v-model="editingSimKa" :localdata="simList" :clear="false"
placeholder="请选择卡号" @change="onSimKaChange"
style="flex: 1; border: none !important; width: 100%;"></uni-data-select>
</view>
</view>
<view class="edit-row">
<text class="edit-label">内容</text>
</view>
<editor id="editor" class="edit-textarea" placeholder="请输入消息内容..." @ready="onEditorReady">
</editor>
</view>
<view class="edit-footer">
<view class="edit-btn cancel" @tap="closeEditPopup">取消</view>
<view class="edit-btn confirm" @tap="confirmEdit">确定</view>
</view>
</view>
</view>
<!-- 排序模式底部工具条 -->
<view v-if="isSortMode" class="sort-toolbar">
<view class="sort-toolbar-tip">长按消息并拖动调整顺序</view>
@ -107,6 +106,61 @@
<view class="sort-toolbar-btn confirm" @tap="confirmSort">完成</view>
</view>
</view>
<!-- 修改当前短信信息弹窗 -->
<view v-if="showAddPopup" class="add-mask" @tap="closeAddPopup">
<view class="add-popup" @tap.stop>
<view class="add-header">编辑当前短信信息</view>
<view class="add-body">
<view class="add-row">
<text class="add-label">头像URL</text>
<view style="position: relative;">
<view class="image-box" :class="addForm.imgShape" style="width: 84rpx;height: 84rpx;"
@tap="selectImage">
<image v-if="addForm.img" class="image w100 h100" :src="addForm.img" mode="aspectFill">
</image>
<image v-else class="image w100 h100" src="/static/image/phone-message/add.png"
mode="aspectFill">
</image>
</view>
<view v-if="addForm.img" class="delete-img-icon" @tap.stop="deleteImage">
×
</view>
</view>
<view v-if="data.phone == 'iphone'"
style="flex: 1;display: flex;align-items: center;justify-content: flex-end;">
<uni-data-checkbox style="display: flex;justify-content: flex-end;"
v-model="addForm.imgShape" :localdata="shape"></uni-data-checkbox>
</view>
</view>
<view class="add-row">
<text class="add-label required">联系人</text>
<input class="add-input" v-model="addForm.title" placeholder="请输入名称或号码" />
</view>
<view class="add-row" v-if="data.phone == 'oppo'">
<text class="add-label">地区</text>
<input class="add-input" v-model="addForm.area" placeholder="请输入地区" />
</view>
<view class="add-row between" v-if="data.phone == 'iphone'">
<text class="add-label">取消通知</text>
<switch :checked="addForm.noNotice" @change="addForm.noNotice = !addForm.noNotice" />
</view>
<view class="add-row between">
<text class="add-label">是否未读</text>
<switch :checked="addForm.unRead" @change="addForm.unRead = !addForm.unRead" />
</view>
<view class="add-row"
v-if="addForm.unRead && (data.phone == 'oppo' || data.phone == 'huawei' || data.phone == 'iphone')">
<text class="add-label">未读数量</text>
<input class="add-input" type="number" v-model="addForm.unReadNumber" placeholder="请输入未读数量" />
</view>
</view>
<view class="add-footer">
<view class="add-btn cancel" @tap="closeAddPopup">取消</view>
<view class="add-btn confirm" @tap="confirmAdd">确定</view>
</view>
</view>
</view>
</view>
</template>
@ -378,6 +432,19 @@ const handleChangeSpeaker = () => {
*/
const handleDelete = () => {
const index = messageList.value.findIndex(item => item.id === selectedMessage.value.id)
const deletedMsg = messageList.value[index]
//
// #ifdef APP-PLUS
if (deletedMsg && deletedMsg.type === 'image' && deletedMsg.imgUrl && deletedMsg.imgUrl.includes('_doc/')) {
uni.removeSavedFile({
filePath: deletedMsg.imgUrl,
success: () => console.log('单条图片消息本地文件删除成功:', deletedMsg.imgUrl),
fail: (err) => console.warn('单条图片消息本地文件删除失败:', deletedMsg.imgUrl, err)
})
}
// #endif
messageList.value.splice(index, 1)
closeActionPopup();
saveChatList()
@ -440,6 +507,7 @@ onShow(() => {
// #endif
})
const handleSend = (params) => {
console.log(params)
params.id = stringUtil.uuid()
@ -469,6 +537,99 @@ const onDblclickRight = () => {
icon: "none"
})
}
const showAddPopup = ref(false)
const addForm = reactive({
title: '',
img: '',
imgShape: 'circle',
area: '',
unRead: false,
unReadNumber: 1,
noNotice: false
})
const shape = [{ text: '圆形', value: 'circle' }, { text: '方形', value: 'square' }]
const handleTitleClick = () => {
const item = data.data
addForm.title = item.title || ''
addForm.img = item.img || ''
addForm.imgShape = item.imgShape || 'circle'
addForm.area = item.area || ''
addForm.unRead = !!item.unRead
addForm.unReadNumber = item.unReadNumber || 0
addForm.noNotice = item.noNotice || false
showAddPopup.value = true
}
const closeAddPopup = () => {
showAddPopup.value = false
}
const selectImage = () => {
uni.chooseImage({
count: 1,
success: (res) => {
addForm.img = res.tempFilePaths[0]
}
})
}
const deleteImage = () => {
addForm.img = ''
}
const confirmAdd = async () => {
if (!addForm.title.trim()) {
uni.showToast({ title: '请输入联系人名称', icon: 'none' })
return
}
let finalImgPath = addForm.img
if (addForm.img && !addForm.img.startsWith('_doc') && !addForm.img.startsWith('/static')) {
try {
const res = await new Promise((resolve, reject) => {
uni.saveFile({
tempFilePath: addForm.img,
success: resolve,
fail: reject
})
})
finalImgPath = res.savedFilePath
} catch (e) {
console.error('保存图片失败', e)
}
}
data.data.title = addForm.title.trim()
data.data.img = finalImgPath
data.data.unRead = addForm.unRead
data.data.unReadNumber = addForm.unReadNumber
data.data.noNotice = addForm.noNotice
data.data.imgShape = addForm.imgShape
data.data.area = addForm.area
try {
const cached = uni.getStorageSync(STORAGE_KEY)
let list = cached ? JSON.parse(cached) : []
const idx = list.findIndex(item => item.id == currentId)
if (idx > -1) {
list[idx].title = data.data.title
list[idx].img = data.data.img
list[idx].unRead = data.data.unRead
list[idx].unReadNumber = data.data.unReadNumber
list[idx].noNotice = data.data.noNotice
list[idx].imgShape = data.data.imgShape
list[idx].area = data.data.area
uni.setStorageSync(STORAGE_KEY, JSON.stringify(list))
}
} catch (e) {
console.error(e)
}
closeAddPopup()
}
</script>
<style lang="less" scoped>
@ -563,7 +724,7 @@ const onDblclickRight = () => {
justify-content: space-between;
transform: translateX(-50%);
width: 540rpx;
z-index: 10;
z-index: 999;
}
.action-popup .triangle {
@ -739,4 +900,144 @@ const onDblclickRight = () => {
color: #333333;
text-align: center;
}
/* ===== 添加短信弹窗 ===== */
.add-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1300;
display: flex;
justify-content: center;
align-items: center;
}
.add-popup {
width: 88%;
background-color: #ffffff;
border-radius: 24rpx;
overflow: hidden;
}
.add-header {
padding: 36rpx 40rpx 20rpx;
font-size: 34rpx;
font-weight: bold;
color: #1A1A1A;
text-align: center;
}
.add-body {
padding: 8rpx 0;
}
.add-row {
display: flex;
align-items: center;
padding: 20rpx 32rpx;
gap: 16rpx;
.image-box {
width: 76rpx;
height: 76rpx;
border-radius: 50%;
overflow: hidden;
.image {
width: 100%;
height: 100%;
}
}
.circle {
border-radius: 50%;
}
.square {
border-radius: 16rpx;
}
}
.between {
justify-content: space-between;
}
.required {
position: relative;
}
.required::before {
position: absolute;
left: -10px;
content: '*';
top: 0;
color: #EA0000;
}
.add-label {
font-size: 28rpx;
color: #1A1A1A;
width: 150rpx;
flex-shrink: 0;
}
.add-input {
flex: 1;
height: 70rpx;
background-color: #F6F6F6;
border-radius: 14rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #1A1A1A;
::v-deep .uni-input {
color: #aaaaaa;
}
}
.add-footer {
padding: 32rpx 24rpx;
display: flex;
}
.add-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
background-color: #F1F1F1;
margin: 0 16rpx;
border-radius: 12rpx;
}
.add-btn.cancel {
color: #767676;
}
.add-btn.confirm {
color: #fff;
background-color: #1777FF;
}
.delete-img-icon {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
padding-bottom: 2rpx;
z-index: 2;
}
</style>

View File

@ -30,13 +30,18 @@
<view class="add-body">
<view class="add-row">
<text class="add-label">头像URL</text>
<view class="image-box" :class="addForm.imgShape" style="width: 84rpx;height: 84rpx;"
@tap="selectImage">
<image v-if="addForm.img" class="image w100 h100" :src="addForm.img" mode="aspectFill">
</image>
<image v-else class="image w100 h100" src="/static/image/phone-message/add.png"
mode="aspectFill">
</image>
<view style="position: relative;">
<view class="image-box" :class="addForm.imgShape" style="width: 84rpx;height: 84rpx;"
@tap="selectImage">
<image v-if="addForm.img" class="image w100 h100" :src="addForm.img" mode="aspectFill">
</image>
<image v-else class="image w100 h100" src="/static/image/phone-message/add.png"
mode="aspectFill">
</image>
</view>
<view v-if="addForm.img" class="delete-img-icon" @tap.stop="deleteImage">
×
</view>
</view>
<view v-if="data.phone == 'iphone'"
style="flex: 1;display: flex;align-items: center;justify-content: flex-end;">
@ -210,7 +215,7 @@ onShow(() => {
if (cachedNotice !== '') {
data.noticeCount = Number(cachedNotice)
}
} catch (e) { }
} catch (e) { console.error(e) }
})
@ -228,7 +233,7 @@ onPageScroll((e) => {
* @returns {Promise<string>} 持久化后的文件路径
*/
const saveImageToLocal = (tempFilePath) => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
//
if (!tempFilePath || tempFilePath.startsWith('_doc') || tempFilePath.startsWith('/static')) {
return resolve(tempFilePath)
@ -277,10 +282,18 @@ const deleteItem = (item) => {
if (res.confirm) {
const idx = defaultList.value.findIndex(i => i.id === item.id)
if (idx > -1) {
//
//
if (item.img) {
removeLocalFile(item.img)
}
//
if (item.chatList && item.chatList.length > 0) {
item.chatList.forEach(msg => {
if (msg.type === 'image' && msg.imgUrl && msg.imgUrl.includes('_doc/')) {
removeLocalFile(msg.imgUrl)
}
})
}
defaultList.value.splice(idx, 1)
uni.setStorageSync(STORAGE_KEY, JSON.stringify(defaultList.value))
}
@ -303,6 +316,10 @@ const selectImage = () => {
})
}
const deleteImage = () => {
addForm.img = ''
}
const itemClick = (item) => {
util.goPage(`/pages/message/chat-page/chat-page?phone=${data.phone}&id=${item.id}`)
}
@ -439,7 +456,7 @@ const confirmAdd = async () => {
})
try {
uni.setStorageSync(STORAGE_KEY, JSON.stringify(defaultList.value))
} catch (e) { }
} catch (e) { console.error(e) }
closeAddPopup()
}
@ -474,7 +491,7 @@ const confirmSim = () => {
sim2: simForm.sim2.trim() || '移动'
}))
uni.showToast({ title: '保存成功', icon: 'success' })
} catch (e) { }
} catch (e) { console.error(e) }
closeSimPopup()
}
@ -498,7 +515,7 @@ const confirmNotice = () => {
try {
uni.setStorageSync(NOTICE_COUNT_KEY, data.noticeCount)
uni.showToast({ title: '保存成功', icon: 'success' })
} catch (e) { }
} catch (e) { console.error(e) }
closeNoticePopup()
}
@ -738,4 +755,22 @@ page {
flex-shrink: 0;
}
}
.delete-img-icon {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 32rpx;
height: 32rpx;
background-color: red;
color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
line-height: 28rpx;
padding-bottom: 2rpx;
z-index: 2;
}
</style>

View File

@ -16,10 +16,18 @@
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app'
import {
ref,
reactive,
getCurrentInstance
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app'
const { proxy } = getCurrentInstance();
const {
proxy
} = getCurrentInstance();
const data = reactive({
phone: ''
})
@ -36,51 +44,50 @@ onLoad((options) => {
})
})
const menuList = ref([
{
name: "京东",
icon: "jingdong",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
url: "/pages/shopping/jingdong/list-index"
}, {
name: "淘宝",
icon: "taobao",
bgColor: ["#FFF0DA", "#FFFFFF"],
btnBg: "#FF953C",
shadowColor: "#FF953C",
url: "/pages/shopping/taobao/list-index"
}, {
name: "快手",
icon: "kuaishou",
bgColor: ["#FFF0DA", "#FFFFFF"],
btnBg: "#FF953C",
shadowColor: "#FF953C",
url: ""
}, {
name: "抖音",
icon: "douyin",
bgColor: ["#FAE5FF", "#FFFFFF"],
btnBg: "#393939",
shadowColor: "#D15CFF",
url: ""
}, {
name: "得物",
icon: "dewu",
bgColor: ["#FAE5FF", "#FFFFFF"],
btnBg: "#393939",
shadowColor: "#D15CFF",
url: ""
}, {
name: "拼多多",
icon: "pinduoduo",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
url: ""
}
]);
const menuList = ref([{
name: "京东",
icon: "jingdong",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
url: "/pages/shopping/jingdong/list-index"
}, {
name: "淘宝",
icon: "taobao",
bgColor: ["#FFF0DA", "#FFFFFF"],
btnBg: "#FF953C",
shadowColor: "#FF953C",
url: "/pages/shopping/taobao/list-index"
}, {
name: "拼多多",
icon: "pinduoduo",
bgColor: ["#FFE0DF", "#FFFFFF"],
btnBg: "#FF4848",
shadowColor: "#FF4848",
// url: "/pages/shopping/pdd/list-index"
url: ""
}, {
name: "快手",
icon: "kuaishou",
bgColor: ["#FFF0DA", "#FFFFFF"],
btnBg: "#FF953C",
shadowColor: "#FF953C",
url: ""
}, {
name: "抖音",
icon: "douyin",
bgColor: ["#FAE5FF", "#FFFFFF"],
btnBg: "#393939",
shadowColor: "#D15CFF",
url: ""
}, {
name: "得物",
icon: "dewu",
bgColor: ["#FAE5FF", "#FFFFFF"],
btnBg: "#393939",
shadowColor: "#D15CFF",
url: ""
}]);
const goTo = (url) => {
if (url) {
@ -170,4 +177,4 @@ page {
text-align: center;
font-weight: 500;
}
</style>
</style>

View File

@ -0,0 +1,479 @@
<template>
<view class="list-card-container" v-if="item">
<!-- 头部店铺信息和状态 -->
<view class="header-box flex-between flex-align-center">
<view class="shop-info flex-align-center">
<view class="shop-logo-placeholder flex-center" v-if="item.isBrand"><text class="logo-text">正品</text>
</view>
<text class="shop-name">{{ item.shopName }}</text>
<!-- <view class="tag-brand" v-if="item.isBrand">品牌认证</view> -->
<image v-if="item.isBrand" class="brand-img" src="/static/image/shopping/pdd/pinpairenzheng.png">
</image>
<image class="flag-img" v-if="item.isFlagship" src="/static/image/shopping/pdd/qijiandian.png"></image>
<uni-icons style="margin-top: 4rpx;" type="right" color="#999999" size="12"></uni-icons>
</view>
<view class="status-text" v-if="item.orderType === 'wait_pay'">待支付</view>
<view class="status-text" v-if="item.orderType === 'wait_recv'">待收货</view>
<view class="status-text" v-if="item.orderType === 'received'">交易成功</view>
</view>
<!-- 商品主要内容 -->
<view class="product-box flex">
<view class="product-img-box">
<image class="product-img" :src="item.productImg" mode="aspectFill"></image>
</view>
<view class="product-content flex-1">
<view class="title-box">
<view class="brand-tag flex-center">品牌</view>
<text class="title-text">{{ item.productTitle }}</text>
</view>
<view class="spec-text text-ellipsis">{{ item.productSpec }}</view>
<view class="tags-box flex-align-center">
<view class="green-tag" v-for="(tag, index) in item.tags" :key="index">{{ tag }}</view>
</view>
</view>
<view class="price-box flex-column align-end">
<text class="price">¥
<text style="font-size: 30rpx;">{{ Number(item.price).toFixed(1).split('.')[0] + '.' }}</text>
<text style="font-size: 22rpx;">{{ Number(item.price).toFixed(1).split('.')[1] }}</text>
</text>
<text class="count">x{{ item.quantity }}</text>
</view>
</view>
<view>
<!-- 价格汇总 -->
<view class="summary-box flex-between flex-align-center">
<view class="avatar-group flex-align-center">
<image class="avatar" v-for="(avatar, idx) in item.avatars" :key="idx" :src="avatar"
mode="aspectFill">
</image>
</view>
<view class="total-price flex-align-center">
<text class="label">{{ item.orderType === 'wait_pay' ? '应付' : '实付' }}:</text>
<text class="symbol">¥</text>
<text class="amount">{{ item.totalAmount }}</text>
<text class="freight">({{ item.freightText }})</text>
</view>
</view>
<!-- 按钮组 -->
<view class="action-box flex-end flex-align-center">
<template v-if="item.orderType === 'wait_pay'">
<view class="btn plain">取消订单</view>
<view class="btn primary">去支付</view>
</template>
<template v-if="item.orderType === 'wait_recv'">
<text class="more-btn">更多</text>
<view class="btn plain">申请退款</view>
<view class="btn plain">追加评价</view>
<view class="btn primary with-badge">
确认收货
<!-- <view class="badge" v-if="item.collectCount">{{ item.collectCount }}人收藏</view> -->
</view>
</template>
<template v-if="item.orderType === 'received'">
<text class="more-btn">更多</text>
<view class="btn plain">申请退款</view>
<view class="btn plain">追加评价</view>
<view class="btn primary with-badge">
再次拼单
<!-- <view class="badge" v-if="item.collectCount">{{ item.collectCount }}人收藏</view> -->
</view>
</template>
</view>
<!-- 物流状态栏 -->
<view class="delivery-box flex-align-center" v-if="item.deliveryStatus">
<view class="truck-icon flex-center">
<image style="width:32rpx;height: 32rpx;" src="/static/image/shopping/pdd/yunshuzhong.png"></image>
</view>
<view class="delivery-content text-ellipsis flex-1">
<template v-if="item.orderType === 'wait_pay'">
<text class="green-text">{{ item.deliveryStatus }}</text>
<text class="green-text">{{ item.deliveryDetail }}</text>
</template>
<template v-if="item.orderType === 'wait_recv'">
<text class="green-text">{{ item.deliveryStatus }}</text>
<text class="gray-text">{{ item.deliveryDetail }}</text>
</template>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
item: {
type: Object,
default: () => ({})
}
});
</script>
<style lang="less" scoped>
.list-card-container {
background-color: #ffffff;
margin-bottom: 16rpx;
padding: 22rpx 0;
box-sizing: border-box;
padding-bottom: 4rpx;
}
.flex-between {
display: flex;
justify-content: space-between;
}
.flex-align-center {
display: flex;
align-items: center;
}
.flex-end {
display: flex;
justify-content: flex-end;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* Header */
.header-box {
padding: 0 24rpx;
margin-bottom: 24rpx;
.shop-logo-placeholder {
width: 44rpx;
height: 24rpx;
background-color: #E01B13;
border-radius: 20rpx;
margin-right: 8rpx;
.logo-text {
color: #fff;
font-size: 16rpx;
transform: scale(0.8);
}
}
.shop-name {
font-weight: 400;
font-size: 28rpx;
color: #1A1A1A;
line-height: 24rpx;
margin-right: 8rpx;
}
.brand-img {
width: 94rpx;
height: 28rpx;
margin-right: 10rpx;
}
.flag-img {
width: 74rpx;
height: 28rpx;
}
.status-text {
font-weight: 400;
font-size: 28rpx;
color: #DF2E26;
line-height: 28rpx;
}
}
/* Product Info */
.product-box {
padding: 0 24rpx;
padding-bottom: 14rpx;
margin-bottom: 10rpx;
border-bottom: 1rpx solid #EFEFEF;
.product-img-box {
width: 148rpx;
height: 148rpx;
margin-right: 14rpx;
border-radius: 4rpx;
overflow: hidden;
background-color: #f5f5f5;
flex-shrink: 0;
.product-img {
width: 100%;
height: 100%;
}
}
.product-content {
width: 0;
.title-box {
font-size: 26rpx;
color: #1A1A1A;
line-height: 36rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
.brand-tag {
display: inline-block;
background-color: #252525;
color: #fff;
font-size: 22rpx;
line-height: 22rpx;
padding: 4rpx 8rpx;
border-radius: 4rpx;
margin-right: 8rpx;
margin-top: -4rpx;
}
.title-text {
vertical-align: middle;
}
}
.spec-text {
font-size: 26rpx;
color: #999999;
line-height: 26rpx;
margin-top: 6rpx;
margin-bottom: 14rpx;
}
.tags-box {
/* Overriding image style */
.green-tag {
display: inline-flex;
align-items: center;
padding: 0 4rpx;
border-radius: 4rpx;
margin: 0 4rpx;
font-weight: 400;
height: 30rpx;
color: #fff;
background-color: #2BB40C;
font-size: 22rpx;
line-height: 22rpx;
border: none;
}
}
}
.price-box {
margin-left: 10rpx;
flex-shrink: 0;
flex-direction: column;
align-items: flex-end;
.price {
font-size: 24rpx;
color: #999;
}
.count {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.align-end {
align-items: flex-end;
}
}
}
/* Summary */
.summary-box {
position: relative;
margin-bottom: 16rpx;
padding: 0 16rpx;
padding-bottom: 14rpx;
&::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 1px;
transform: scaleY(0.3);
background-color: #D2D2D2;
}
.avatar-group {
position: relative;
width: 110rpx;
height: 44rpx;
.avatar {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
border: 2rpx solid #FFFFFF;
position: absolute;
}
.avatar:nth-child(1) {
left: 0;
z-index: 4;
}
.avatar:nth-child(2) {
left: 34rpx;
z-index: 3;
}
.avatar:nth-child(3) {
left: 68rpx;
z-index: 2;
}
.avatar:nth-child(4) {
left: 102rpx;
z-index: 1;
}
}
.total-price {
display: flex;
align-items: baseline;
font-size: 24rpx;
color: #1A1A1A;
.label {
margin-right: 4rpx;
}
.symbol {
font-size: 24rpx;
margin-right: 4rpx;
}
.amount {
font-size: 34rpx;
}
.freight {
font-size: 26rpx;
margin-left: 8rpx;
}
}
}
/* Action Buttons */
.action-box {
margin-bottom: 16rpx;
padding: 0 16rpx;
.more-btn {
font-weight: 400;
font-size: 28rpx;
color: #1A1A1A;
line-height: 28rpx;
margin-right: 46rpx;
}
.btn {
min-width: 138rpx;
height: 58rpx;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
margin-left: 14rpx;
padding: 0 12rpx;
box-sizing: border-box;
&.plain {
border: 1rpx solid #9E9E9E;
color: #1A1A1A;
background-color: #fff;
}
&.primary {
border: 1rpx solid #DF2E26;
background-color: #DF2E26;
color: #fff;
}
&.with-badge {
position: relative;
.badge {
position: absolute;
top: -18rpx;
right: -12rpx;
background-color: #FFE669;
color: #E01B13;
font-size: 18rpx;
padding: 2rpx 10rpx;
border-radius: 20rpx;
white-space: nowrap;
transform: scale(0.9);
z-index: 2;
}
}
}
}
/* Delivery Info */
.delivery-box {
background-color: #F5F5F5;
padding: 10rpx 16rpx;
border-radius: 4rpx;
margin: 0 16rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 16rpx;
.truck-icon {
margin-right: 6rpx;
}
.delivery-content {
font-size: 24rpx;
.green-text {
color: #2BB40C;
}
.gray-text {
color: #595959;
margin-left: 10rpx;
}
}
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<view class="page-container flex-column">
<view class="nav-bar">
<nav-bar>
<template v-slot:left>
<image style="width: 48rpx;height: 48rpx;" src="/static/image/shopping/pdd/back.png"></image>
</template>
<template v-slot:center>
<view class="title-box flex-center flex-column">
<view class="title">我的订单</view>
<view class="flex-center text">
<image style="width: 30rpx;height: 30rpx;" src="/static/image/shopping/pdd/safety.png">
</image>
<text>专属客服 ·闪电退换 ·售后无忧</text>
</view>
</view>
</template>
<template v-slot:right>
<image style="width: 48rpx;height: 48rpx;" src="/static/image/shopping/pdd/search.png"></image>
</template>
</nav-bar>
</view>
<view class="tab-list flex-align-center">
<view class="tab-item" :class="{ active: tab.id == activeTab }" v-for="(tab, index) in tabList"
:key="index">
<text>{{ tab.name }}</text>
</view>
</view>
<scroll-view class="flex-1" style="height: 100px;" scroll-y="true">
<list-card v-for="(item, index) in orderList" :key="index" :item="item"></list-card>
<view v-if="orderList.length == 0" class="null-data-box flex-align-center">
<text>没找到订单试试</text>
<text style="color: #2686E4;">查看全部</text>
<text></text>
<text style="color: #2686E4;">切换账号</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import ListCard from './components/list-card/list-card.vue';
const activeTab = ref(0);
const tabList = ref([
{
name: '全部',
id: 0
},
{
name: '待付款',
id: 1
},
{
name: '拼团中',
id: 2
},
{
name: '打包中',
id: 3
},
{
name: '待收货',
id: 4
},
{
name: '评价',
id: 5
},
]);
const orderList = ref([
{
// type: 1,
orderType: "wait_pay",
shopName: '澳松旗舰店',
isBrand: true,
isFlagship: true,
productImg: 'https://picsum.photos/200/200?random=1',
productTitle: '适配哈哈哈哈哈哈哈哈哈哈哈哈头配哈哈哈哈哈哈哈哈哈哈哈哈很好...',
productSpec: '(小头款)哈哈哈哈哈哈哈哈哈哈哈哈很好...',
tags: ['坏了包赔', '7天无理由退货'],
price: '35.9',
quantity: 1,
avatars: [
'https://picsum.photos/40/40?random=1',
'https://picsum.photos/40/40?random=2',
'https://picsum.photos/40/40?random=3',
'https://picsum.photos/40/40?random=4'
],
totalAmount: '35.9',
freightText: '免运费',
deliveryStatus: '现在支付预计6小时内发货',
deliveryDetail: '后天送达'
},
{
// type: 2,
orderType: "wait_recv",
shopName: '澳松旗舰店',
isBrand: true,
isFlagship: true,
productImg: 'https://picsum.photos/200/200?random=2',
productTitle: '适配哈哈哈哈哈哈哈哈哈哈哈哈头配哈哈哈哈哈哈哈哈哈哈哈哈很好...',
productSpec: '(小头款)哈哈哈哈哈哈哈哈哈哈哈哈很好...',
tags: ['7天无理由退货'],
price: '35.9',
quantity: 1,
avatars: [
'https://picsum.photos/40/40?random=5',
'https://picsum.photos/40/40?random=6',
'https://picsum.photos/40/40?random=7',
'https://picsum.photos/40/40?random=8'
],
totalAmount: '35.9',
freightText: '免运费',
collectCount: 558,
deliveryStatus: '运输中 预计明天送达',
deliveryDetail: '申通快递:【洛阳市】快件已发往 新...'
},
{
// type: 3,
orderType: "received",
shopName: '澳松旗舰店',
isBrand: true,
isFlagship: true,
productImg: 'https://picsum.photos/200/200?random=3',
productTitle: '适配哈哈哈哈哈哈哈哈哈哈哈哈头配哈哈哈哈哈哈哈哈哈哈哈哈很好...',
productSpec: '(小头款)哈哈哈哈哈哈哈哈哈哈哈哈很好...',
tags: ['退货包运费保障中'],
price: '35.9',
quantity: 1,
avatars: [
'https://picsum.photos/40/40?random=9',
'https://picsum.photos/40/40?random=10',
'https://picsum.photos/40/40?random=11',
'https://picsum.photos/40/40?random=12'
],
totalAmount: '35.9',
freightText: '免运费',
collectCount: 558
}
]);
</script>
<style lang="less" scoped>
.page-container {
height: 100vh;
}
.nav-bar {
border-bottom: 1rpx solid #D2D2D2;
}
.title-box {
display: flex;
flex-direction: column;
.title {
font-weight: 400;
font-size: 30rpx;
color: #1A1A1A;
line-height: 30rpx;
}
.text {
font-weight: 400;
font-size: 24rpx;
color: #16B800;
line-height: 24rpx;
margin-top: 12rpx;
}
}
.tab-list {
height: 80rpx;
background-color: #ffff;
border-bottom: 0.5px solid #D2D2D2;
.tab-item {
font-weight: 400;
font-size: 30rpx;
color: #1A1A1A;
line-height: 80rpx;
white-space: nowrap;
margin: 0 22rpx;
&.active {
color: #E01B13;
font-weight: 500;
border-bottom: 4rpx solid #E01B13;
}
}
}
.null-data-box {
justify-content: center;
font-size: 28rpx;
color: #999999;
line-height: 28rpx;
padding: 42rpx 0;
}
</style>
<style>
@import '@/common/main.css';
page {
background-color: #F5F5F5;
}
</style>

View File

@ -0,0 +1,376 @@
<template>
<view class="page-container flex-column">
<view class="hightlight-box">
<nav-bar>
<template v-slot:center>
<view class="flex-column flex-center">
<view class="title">待付款 预计后天送达</view>
<view class="sub-title">请在23:59:27.2内支付逾期订单将自动取消</view>
</view>
</template>
</nav-bar>
</view>
<view class="adress-box flex-align-center flex-justify-between">
<image style="width:40rpx;height: 40rpx;flex-shrink: 0;" src="/static/image/shopping/pdd/location.png">
</image>
<view class="user-info flex-1">
<view>
<text>姓名</text> <text>电话</text>
</view>
<view>
<text>地址</text>
</view>
</view>
<view v-if="order.orderType == 'wait_pay'" class="edit-btn">修改</view>
</view>
<view class="upload-shop-logo-box flex-justify-between flex-align-center">
<text>上传店铺头像</text>
<view>
</view>
</view>
<view class="product-card">
<!-- 店铺信息 -->
<view class="shop-header flex-align-center">
<view class="shop-logo flex-center">
<text>卫松</text>
</view>
<text class="shop-name">卫松旗舰店</text>
<image class="brand-auth-tag" src="/static/image/shopping/pdd/pinpairenzheng.png" mode="heightFix">
</image>
<image class="flagship-tag" src="/static/image/shopping/pdd/qijiandian.png" mode="heightFix"></image>
</view>
<!-- 商品主要内容 -->
<view class="product-content flex">
<view class="img-box">
<image class="product-img"
src="https://img.pddpic.com/mms-material-img/2022-09-02/76b7e127-ecdf-41ef-8d69-a36c561570d5.png"
mode="aspectFill"></image>
</view>
<view class="info-box flex-1">
<view class="title-price flex">
<view class="title text-ellipsis-2 flex-1">
云南傣味 头配哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
</view>
<view class="price-box flex-column align-end">
<text class="price">¥35.9</text>
<text class="count">x1</text>
</view>
</view>
<view class="spec text-ellipsis-2">
傣味头款哈哈哈哈哈哈哈哈哈哈哈哈哈很哈哈哈哈哈哈
</view>
<view class="tags flex-align-center">
<view class="green-tag">坏了包赔<uni-icons type="right" size="10" color="#fff"
style="margin-left: 2rpx;"></uni-icons></view>
<view class="green-tag">7天无理由退货<uni-icons type="right" size="10" color="#fff"
style="margin-left: 2rpx;"></uni-icons></view>
</view>
</view>
</view>
<!-- 联系商家按钮 -->
<view class="contact-box flex">
<view style="flex: 1;"></view>
<view class="contact-btn flex-center">
<view class="chat-icon">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
<text>联系商家</text>
</view>
</view>
<view class="divider"></view>
<!-- 底部支付金额 -->
<view class="payment-box flex-align-center">
<view style="flex: 1;"></view>
<text class="label">应付: </text>
<text class="symbol"></text>
<text class="amount">16.9</text>
<text class="freight">(免运费)</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const order = ref({
orderType: 'wait_pay'
})
</script>
<style lang="less" scoped>
.title {
font-weight: 500;
font-size: 36rpx;
color: #1A1A1A;
line-height: 36rpx;
}
.sub-title {
font-weight: 400;
font-size: 26rpx;
color: #DF2E26;
line-height: 26rpx;
white-space: nowrap;
margin-top: 14rpx;
}
.hightlight-box {
border-bottom: 1rpx solid #F4F4F4;
}
.adress-box {
background-color: #ffffff;
padding: 24rpx 26rpx;
margin-bottom: 18rpx;
.user-info {
margin-left: 22rpx;
margin-right: 56rpx;
color: #535353;
font-size: 30rpx;
}
.edit-btn {
flex-shrink: 0;
font-weight: 400;
font-size: 30rpx;
line-height: 30rpx;
color: #DF2E26;
border-radius: 8rpx;
border: 1rpx solid #DF2E26;
text-align: center;
padding: 16rpx 22rpx;
}
}
.product-card {
background-color: #ffffff;
padding: 24rpx 24rpx 0;
margin-bottom: 20rpx;
.shop-header {
margin-bottom: 24rpx;
.shop-logo {
width: 48rpx;
height: 26rpx;
background-color: #E02020;
border-radius: 13rpx;
margin-right: 12rpx;
position: relative;
box-shadow: 0 0 0 2rpx #fff, 0 0 0 4rpx #E02020;
text {
font-size: 16rpx;
color: #fff;
font-weight: bold;
transform: scale(0.8);
}
}
.shop-name {
font-size: 30rpx;
color: #151515;
margin-right: 12rpx;
font-weight: 500;
}
.brand-auth-tag {
height: 30rpx;
margin-right: 8rpx;
}
.flagship-tag {
height: 30rpx;
}
}
.product-content {
margin-bottom: 24rpx;
.img-box {
width: 156rpx;
height: 156rpx;
border-radius: 8rpx;
background-color: #f5f5f5;
margin-right: 18rpx;
flex-shrink: 0;
overflow: hidden;
.product-img {
width: 100%;
height: 100%;
}
}
.info-box {
width: 0;
.title-price {
align-items: flex-start;
.title {
font-size: 28rpx;
color: #151515;
line-height: 40rpx;
font-weight: 400;
margin-right: 20rpx;
}
.price-box {
flex-shrink: 0;
.price {
font-size: 26rpx;
color: #999999;
line-height: 36rpx;
}
.count {
font-size: 24rpx;
color: #999999;
margin-top: 4rpx;
text-align: right;
}
}
}
.spec {
font-size: 26rpx;
color: #999999;
line-height: 36rpx;
margin-top: 4rpx;
margin-bottom: 12rpx;
}
.tags {
.green-tag {
background-color: #49B113;
color: #ffffff;
font-size: 22rpx;
line-height: 22rpx;
padding: 6rpx 4rpx 6rpx 8rpx;
border-radius: 6rpx;
margin-right: 10rpx;
display: inline-flex;
align-items: center;
}
}
}
}
.contact-box {
padding-bottom: 24rpx;
.contact-btn {
height: 56rpx;
padding: 0 24rpx;
border-radius: 8rpx;
border: 1rpx solid #DEDEDE;
.chat-icon {
width: 28rpx;
height: 24rpx;
border-radius: 6rpx;
border: 2rpx solid #49B113;
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-right: 10rpx;
&::after {
content: '';
position: absolute;
bottom: -6rpx;
left: 4rpx;
width: 0;
height: 0;
border-left: 6rpx solid transparent;
border-right: 6rpx solid transparent;
border-top: 8rpx solid #49B113;
transform: rotate(30deg);
}
.dot {
width: 4rpx;
height: 4rpx;
background-color: #49B113;
border-radius: 50%;
margin: 0 1rpx;
}
}
text {
font-size: 26rpx;
color: #151515;
}
}
}
.divider {
height: 1px;
background-color: #EFEFEF;
transform: scaleY(0.5);
width: 100%;
}
.payment-box {
padding: 24rpx 0;
.label {
font-size: 26rpx;
color: #151515;
}
.symbol {
font-size: 24rpx;
color: #DF2E26;
margin-left: 10rpx;
font-weight: 500;
}
.amount {
font-size: 36rpx;
color: #DF2E26;
font-weight: 500;
}
.freight {
font-size: 26rpx;
color: #151515;
margin-left: 8rpx;
}
}
}
.text-ellipsis-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.align-end {
align-items: flex-end;
}
.upload-shop-logo-box {
background-color: #fff;
padding: 24rpx 26rpx;
margin-bottom: 18rpx;
}
</style>
<style>
@import '@/common/main.css';
</style>

View File

@ -35,7 +35,7 @@
<view class="goods-title-row">
<view class="good-info-box" :id="'title-box-' + index + '-0'">
<text class="goods-title">{{ getTruncatedTitle(item, index, item.goodsList[0], 0)
}}</text>
}}</text>
<view class="goods-spec" style="margin-top: 18rpx; color: #999;">默认</view>
<view class="goods-tags" v-if="item.goodsList[0].tags || item.goodsList[0].tag"
style="margin-top: 18rpx;">
@ -118,7 +118,7 @@
<text>好评</text>
</view>
</view>
<!-- 单个商品的售后状态 -->
<!-- 单个商品的退款状态 -->
<view class="goods-evaluation" v-if="item.statusType == 'refunding' && currentFilter != 4">
<view class="left">
<image style="width: 24rpx;height: 24rpx;margin-right: 8rpx;"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB