campus-errand/miniapp/pages/message/chat.vue
2026-03-25 20:53:34 +08:00

1124 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="chat-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">聊天</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 顶部订单信息卡片 -->
<view class="order-card" v-if="orderInfo.id">
<view class="order-card-body">
<view class="order-card-left">
<view class="order-info-row">
<text class="order-info-label">订单类型</text>
<text class="order-info-val">{{ formatOrderType(orderInfo.orderType) }}</text>
</view>
<view class="order-info-row" v-if="orderInfo.itemName">
<text class="order-info-label">{{ getItemLabel(orderInfo.orderType) }}</text>
<text class="order-info-val">{{ orderInfo.itemName }}</text>
</view>
<view class="order-info-row" v-if="orderInfo.pickupLocation">
<text class="order-info-label">取货地址</text>
<text class="order-info-val">{{ orderInfo.pickupLocation }}</text>
</view>
<view class="order-info-row" v-if="orderInfo.deliveryLocation">
<text class="order-info-label">送达地址</text>
<text class="order-info-val">{{ orderInfo.deliveryLocation }}</text>
</view>
<view class="order-info-row">
<text class="order-info-label">备注信息</text>
<text class="order-info-val">{{ orderInfo.remark || '无' }}</text>
</view>
</view>
<view class="order-card-right">
<view class="order-commission">
<text class="commission-label">跑腿费</text>
<text class="commission-value">{{ orderInfo.commission }}</text>
</view>
<view class="order-detail-btn" @click="goOrderDetail">
<text>查看详情</text>
</view>
</view>
</view>
</view>
<!-- 聊天记录区域 -->
<scroll-view class="chat-body" scroll-y :scroll-top="scrollTop" scroll-with-animation
@scrolltoupper="loadMoreHistory">
<!-- 聊天安全提示 -->
<view class="chat-safety-tip">
<text>聊天安全提示请友好和谐沟通请友好和谐沟通请友好和谐沟通请友好和谐沟通</text>
</view>
<view v-if="loadingHistory" class="loading-history">
<text>加载中...</text>
</view>
<view v-for="(msg, index) in chatMessages" :key="msg.id || index">
<!-- 时间戳 -->
<view v-if="msg.showTime" class="msg-time">
<text>{{ msg.timeLabel }}</text>
</view>
<!-- 系统提示消息 -->
<view v-if="msg.type === 'system'" class="msg-system-wrap">
<view class="msg-system">
<text>{{ msg.content }}</text>
</view>
</view>
<!-- 改价申请消息 -->
<view v-else-if="msg.type === 'price-change'" class="msg-system-wrap">
<view class="price-change-card">
<view class="pc-header">
<text class="pc-title">{{ msg.isSelf ? '您' : '对方' }}发起了{{ msg.changeTypeLabel }}改价申请</text>
</view>
<view class="pc-body">
<view class="pc-row">
<text class="pc-label">原价</text>
<text class="pc-value">¥{{ msg.originalPrice }}</text>
</view>
<view class="pc-row">
<text class="pc-label">新价</text>
<text class="pc-value pc-new-price">¥{{ msg.newPrice }}</text>
</view>
<view class="pc-row" v-if="msg.difference !== 0">
<text class="pc-label">{{ msg.difference > 0 ? '需补缴' : '将退款' }}</text>
<text class="pc-value" :class="msg.difference > 0 ? 'pc-pay' : 'pc-refund'">
¥{{ Math.abs(msg.difference).toFixed(2) }}
</text>
</view>
</view>
<view v-if="msg.status === 'Pending' && !msg.isSelf" class="pc-actions">
<view class="pc-btn pc-accept" @click="respondPriceChange(msg.priceChangeId, 'Accepted')">
<text>同意</text>
</view>
<view class="pc-btn pc-reject" @click="respondPriceChange(msg.priceChangeId, 'Rejected')">
<text>拒绝</text>
</view>
</view>
<view v-else-if="msg.status === 'Pending' && msg.isSelf" class="pc-waiting">
<text>等待对方确认中</text>
</view>
<view v-else class="pc-result">
<text :class="msg.status === 'Accepted' ? 'pc-accepted' : 'pc-rejected'">
{{ msg.status === 'Accepted' ? '已同意' : '已拒绝' }}
</text>
</view>
</view>
</view>
<!-- 普通消息 -->
<view v-else class="msg-row" :class="{ 'msg-self': msg.isSelf }">
<image class="msg-avatar" :src="msg.avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="msg-bubble">
<image v-if="msg.type === 'image'" class="msg-image" :src="msg.content" mode="widthFix"
@click="previewImage(msg.content)"></image>
<text v-else class="msg-text">{{ msg.content }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 快捷操作栏 -->
<view class="quick-actions" v-if="orderInfo.id">
<view class="quick-btn" @click="onCallPhone">
<text>拨打电话</text>
</view>
<view class="quick-btn" @click="onContactService">
<text>联系客服</text>
</view>
</view>
<!-- 底部输入区域 -->
<view class="chat-bottom">
<view class="footer-input-row">
<input class="chat-input" v-model="inputText" placeholder="请输入..." confirm-type="send"
@confirm="sendMessage" @focus="showMorePanel = false" />
<view class="send-btn" @click="sendMessage">
<text>发送</text>
</view>
<view class="plus-btn" :class="{ active: showMorePanel }" @click="toggleMorePanel">
<image class="plus-icon" src="/static/ic_add.png" mode="aspectFit"></image>
</view>
</view>
<!-- 内嵌更多面板 -->
<view class="more-panel" v-if="showMorePanel">
<view class="panel-item" @click="onSendImage">
<view class="panel-icon-wrap"><image class="panel-icon-img" src="/static/ic_picture.png" mode="aspectFit"></image></view>
<text class="panel-label">发送图片</text>
</view>
<view class="panel-item" v-if="showGoodsPriceBtn" @click="onChangeGoodsPrice">
<view class="panel-icon-wrap"><image class="panel-icon-img" src="/static/ic_commodity.png" mode="aspectFit"></image></view>
<text class="panel-label">更改商品价格</text>
</view>
<view class="panel-item" @click="onChangeCommission">
<view class="panel-icon-wrap"><image class="panel-icon-img" src="/static/ic_errand.png" mode="aspectFit"></image></view>
<text class="panel-label">更改跑腿价格</text>
</view>
<view class="panel-item" v-if="isRunner" @click="onCompleteOrder">
<view class="panel-icon-wrap"><image class="panel-icon-img" src="/static/ic_complete.png" mode="aspectFit"></image></view>
<text class="panel-label">完成订单</text>
</view>
</view>
</view>
<!-- 改价弹窗 -->
<view v-if="showPriceChangePopup" class="popup-mask" @click="showPriceChangePopup = false">
<view class="popup-content" @click.stop>
<text class="popup-title">{{ priceChangeTypeLabel }}</text>
<view class="popup-field">
<text class="popup-label">当前价格</text>
<text class="popup-current-price">¥{{ currentPriceForChange }}</text>
</view>
<view class="popup-field">
<text class="popup-label">新价格</text>
<input class="popup-input" type="digit" v-model="newPriceInput" placeholder="请输入新价格" />
</view>
<view v-if="priceDifference !== 0" class="popup-diff">
<text v-if="priceDifference > 0 && isOwner" class="pc-pay">需补缴
¥{{ priceDifference.toFixed(2) }}</text>
<text v-else-if="priceDifference < 0" class="pc-refund">将退款
¥{{ Math.abs(priceDifference).toFixed(2) }}</text>
<text v-else-if="priceDifference > 0 && !isOwner" class="pc-pay">对方需补缴
¥{{ priceDifference.toFixed(2) }}</text>
</view>
<view class="popup-actions">
<view class="popup-btn popup-cancel" @click="showPriceChangePopup = false"><text>取消</text></view>
<view class="popup-btn popup-confirm" @click="submitPriceChange"><text>发起修改申请</text></view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
getOrderDetail,
getOrderByChatUser,
createPriceChange,
respondPriceChange as respondPriceChangeApi
} from '../../utils/api'
import {
useUserStore
} from '../../stores/user'
import {
initIM,
logoutIM,
onNewMessage,
offNewMessage,
getMessageList,
sendTextMessage,
sendImageMessage,
sendCustomMessage,
formatIMMessage
} from '../../utils/im'
export default {
data() {
return {
orderId: null,
orderInfo: {},
chatMessages: [],
inputText: '',
scrollTop: 0,
showMorePanel: false,
imUserId: '',
targetImUserId: '',
imReady: false,
nextReqMessageID: '',
historyCompleted: false,
loadingHistory: false,
showPriceChangePopup: false,
priceChangeType: '',
newPriceInput: '',
submittingPriceChange: false,
statusBarHeight: 0
}
},
computed: {
isRunner() {
const userStore = useUserStore()
return this.orderInfo.runnerId === userStore.userId
},
isOwner() {
const userStore = useUserStore()
return this.orderInfo.ownerId === userStore.userId
},
showGoodsPriceBtn() {
const type = this.orderInfo.orderType
return type === 'Purchase' || type === 'Food'
},
priceChangeTypeLabel() {
return this.priceChangeType === 'Commission' ? '更改跑腿价格' : '更改商品价格'
},
currentPriceForChange() {
if (this.priceChangeType === 'Commission') return (this.orderInfo.commission || 0).toFixed(2)
return (this.orderInfo.goodsAmount || 0).toFixed(2)
},
priceDifference() {
const newPrice = parseFloat(this.newPriceInput)
if (isNaN(newPrice) || newPrice < 0) return 0
const original = this.priceChangeType === 'Commission' ?
(this.orderInfo.commission || 0) : (this.orderInfo.goodsAmount || 0)
return newPrice - original
}
},
async onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
console.log('[聊天] onLoad options:', JSON.stringify(options))
this.orderId = options.orderId || null
this.targetImUserIdFromParam = options.targetUserId || null
if (this.orderId) await this.loadOrderInfo()
await this.loginIM()
},
onUnload() {
offNewMessage()
},
onShow() {
// 如果 orderId 存在但 orderInfo 为空,重新加载
if (this.orderId && !this.orderInfo.id) {
this.loadOrderInfo()
}
},
methods: {
goBack() {
uni.navigateBack()
},
async loadOrderInfo() {
if (!this.orderId) return
try {
const res = await getOrderDetail(this.orderId);
console.log('[聊天] 订单详情:', res)
this.orderInfo = res || {}
} catch (e) {
console.error('[聊天] 加载订单详情失败:', e)
}
},
async loginIM() {
try {
const {
userId
} = await initIM()
this.imUserId = userId
if (this.targetImUserIdFromParam) {
this.targetImUserId = this.targetImUserIdFromParam
} else if (this.orderInfo.id) {
const userStore = useUserStore()
this.targetImUserId = this.orderInfo.ownerId === userStore.userId ?
`user_${this.orderInfo.runnerId}` : `user_${this.orderInfo.ownerId}`
}
this.imReady = true
// 如果没有 orderId 但有对方用户ID查找关联订单
if (!this.orderId && this.targetImUserId) {
try {
const targetUid = this.targetImUserId.replace('user_', '')
const res = await getOrderByChatUser(targetUid)
if (res && res.found && res.orderId) {
this.orderId = res.orderId
await this.loadOrderInfo()
}
} catch (e) {
console.error('[聊天] 查找关联订单失败:', e)
}
}
onNewMessage((msgList) => {
for (const msg of msgList) {
if (msg.from === this.targetImUserId || msg.from === this.imUserId) {
this.chatMessages.push(formatIMMessage(msg, this.imUserId))
}
}
this.scrollToBottom()
})
await this.loadHistory()
} catch (e) {
console.error('[IM] 初始化失败:', e)
uni.showToast({
title: 'IM连接失败',
icon: 'none'
})
}
},
async loadHistory() {
if (!this.imReady || !this.targetImUserId) return
this.loadingHistory = true
try {
const res = await getMessageList(this.targetImUserId, this.nextReqMessageID)
const formatted = res.messageList.map(m => formatIMMessage(m, this.imUserId))
this.chatMessages = [...formatted, ...this.chatMessages]
this.nextReqMessageID = res.nextReqMessageID
this.historyCompleted = res.isCompleted
this.scrollToBottom()
} catch (e) {
console.error('[IM] 拉取历史消息失败:', e)
} finally {
this.loadingHistory = false
}
},
async loadMoreHistory() {
if (this.historyCompleted || this.loadingHistory) return
await this.loadHistory()
},
async sendMessage() {
if (!this.inputText.trim()) return
if (!this.imReady) {
uni.showToast({
title: 'IM未就绪',
icon: 'none'
});
return
}
const text = this.inputText
this.inputText = ''
try {
const msg = await sendTextMessage(this.targetImUserId, text)
this.chatMessages.push(formatIMMessage(msg, this.imUserId))
this.scrollToBottom()
} catch (e) {
uni.showToast({
title: '发送失败',
icon: 'none'
});
this.inputText = text
}
},
scrollToBottom() {
this.$nextTick(() => {
this.scrollTop = this.scrollTop === 99999 ? 99998 : 99999
})
},
toggleMorePanel() {
this.showMorePanel = !this.showMorePanel
},
onSendImage() {
this.showMorePanel = false
if (!this.imReady) {
uni.showToast({
title: 'IM未就绪',
icon: 'none'
});
return
}
uni.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
success: async (res) => {
try {
const msg = await sendImageMessage(this.targetImUserId, res.tempFilePaths[0])
this.chatMessages.push(formatIMMessage(msg, this.imUserId))
this.scrollToBottom()
} catch (e) {
uni.showToast({
title: '图片发送失败',
icon: 'none'
})
}
}
})
},
onChangeCommission() {
this.showMorePanel = false;
this.priceChangeType = 'Commission'
this.newPriceInput = '';
this.showPriceChangePopup = true
},
onChangeGoodsPrice() {
this.showMorePanel = false;
this.priceChangeType = 'GoodsAmount'
this.newPriceInput = '';
this.showPriceChangePopup = true
},
async submitPriceChange() {
const newPrice = parseFloat(this.newPriceInput)
if (isNaN(newPrice) || newPrice < 0) {
uni.showToast({
title: '请输入有效的价格',
icon: 'none'
});
return
}
if (this.submittingPriceChange) return
this.submittingPriceChange = true
if (this.isOwner && this.priceDifference > 0) {
uni.showModal({
title: '补缴支付',
content: `需补缴 ¥${this.priceDifference.toFixed(2)},确认?`,
success: async (r) => {
if (r.confirm) await this.doSubmitPriceChange(newPrice)
this.submittingPriceChange = false
}
});
return
}
await this.doSubmitPriceChange(newPrice)
this.submittingPriceChange = false
},
async doSubmitPriceChange(newPrice) {
try {
const res = await createPriceChange(this.orderId, {
changeType: this.priceChangeType,
newPrice
})
this.showPriceChangePopup = false
const changeTypeLabel = this.priceChangeType === 'Commission' ? '跑腿佣金' : '商品总额'
if (this.imReady) {
await sendCustomMessage(this.targetImUserId, {
bizType: 'price-change',
priceChangeId: res.id,
changeTypeLabel,
originalPrice: res.originalPrice.toFixed(2),
newPrice: res.newPrice.toFixed(2),
difference: res.difference,
status: res.status,
description: `发起了${changeTypeLabel}改价申请`
})
}
this.chatMessages.push({
id: `pc_${res.id}`,
type: 'price-change',
isSelf: true,
priceChangeId: res.id,
changeTypeLabel,
originalPrice: res.originalPrice.toFixed(2),
newPrice: res.newPrice.toFixed(2),
difference: res.difference,
status: res.status
})
this.scrollToBottom()
uni.showToast({
title: '改价申请已发送',
icon: 'none'
})
} catch (e) {
uni.showToast({
title: e.message || '改价申请失败',
icon: 'none'
})
}
},
async respondPriceChange(priceChangeId, action) {
try {
const res = await respondPriceChangeApi(this.orderId, priceChangeId, {
action
})
const msg = this.chatMessages.find(m => m.type === 'price-change' && m.priceChangeId ===
priceChangeId)
if (msg) msg.status = res.status
const actionLabel = action === 'Accepted' ? '同意' : '拒绝'
const changeTypeLabel = res.changeType === 'Commission' ? '跑腿佣金' : '商品总额'
this.chatMessages.push({
id: `sys_${Date.now()}`,
type: 'system',
content: `您已${actionLabel}${changeTypeLabel}改价`
})
if (action === 'Accepted' && res.difference !== 0) {
if (res.difference < 0) this.chatMessages.push({
id: `sys_r_${Date.now()}`,
type: 'system',
content: `已退还您${Math.abs(res.difference).toFixed(2)}`
})
await this.loadOrderInfo()
}
this.scrollToBottom()
} catch (e) {
uni.showToast({
title: e.message || '操作失败',
icon: 'none'
})
}
},
onCompleteOrder() {
this.showMorePanel = false
uni.navigateTo({
url: `/pages/order/complete-order?id=${this.orderId}`
})
},
goOrderDetail() {
uni.navigateTo({
url: `/pages/order/order-detail?id=${this.orderId}`
})
},
onCallPhone() {
const phone = this.isOwner ? this.orderInfo.runnerPhone : this.orderInfo.phone
if (!phone) {
uni.showToast({
title: '暂无对方手机号',
icon: 'none'
});
return
}
uni.makePhoneCall({
phoneNumber: phone,
fail: () => {}
})
},
onContactService() {
uni.navigateTo({
url: '/pages/config/qrcode'
})
},
previewImage(url) {
uni.previewImage({
urls: [url]
})
},
formatOrderType(type) {
const map = {
Pickup: '代取',
Delivery: '代送',
Help: '万能帮',
Purchase: '代购',
Food: '美食街'
}
return map[type] || type
},
getItemLabel(type) {
const map = {
Pickup: '代取物品',
Delivery: '送货物品',
Help: '帮忙事项',
Purchase: '代购物品',
Food: '美食订单'
}
return map[type] || '物品'
}
}
}
</script>
<style scoped>
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.chat-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
/* 订单信息卡片 */
.order-card {
margin: 16rpx 24rpx 0;
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
flex-shrink: 0;
}
.order-card-body {
display: flex;
}
.order-card-left {
flex: 1;
min-width: 0;
}
.order-info-row {
display: flex;
margin-bottom: 6rpx;
line-height: 1.6;
}
.order-info-label {
font-size: 26rpx;
color: #666;
flex-shrink: 0;
}
.order-info-val {
font-size: 26rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.order-card-right {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
margin-left: 20rpx;
flex-shrink: 0;
}
.order-commission {
display: flex;
align-items: baseline;
}
.commission-label {
font-size: 24rpx;
color: #FF6600;
}
.commission-value {
font-size: 36rpx;
color: #FF6600;
font-weight: bold;
}
.order-detail-btn {
background: linear-gradient(135deg, #FFB700, #FF9500);
padding: 12rpx 28rpx;
border-radius: 8rpx;
}
.order-detail-btn text {
font-size: 24rpx;
color: #fff;
font-weight: 500;
}
/* 聊天安全提示 */
.chat-safety-tip {
text-align: center;
padding: 16rpx 40rpx;
flex-shrink: 0;
}
.chat-safety-tip text {
font-size: 22rpx;
color: #ccc;
line-height: 1.6;
}
/* 聊天记录 */
.chat-body {
flex: 1;
padding: 16rpx 0;
overflow-y: auto;
}
.loading-history {
text-align: center;
padding: 16rpx 0;
font-size: 24rpx;
color: #999;
}
.msg-time {
text-align: center;
margin: 16rpx 0;
}
.msg-time text {
font-size: 22rpx;
color: #bbb;
}
/* 消息行 */
.msg-row {
display: flex;
align-items: flex-start;
margin-bottom: 24rpx;
padding: 0 8rpx;
}
.msg-row.msg-self {
flex-direction: row-reverse;
}
.msg-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
flex-shrink: 0;
}
.msg-bubble {
max-width: 60%;
margin: 0 16rpx;
}
.msg-text {
background-color: #fff;
padding: 20rpx 24rpx;
border-radius: 20rpx;
font-size: 28rpx;
color: #333;
word-break: break-all;
display: block;
line-height: 1.5;
}
.msg-self .msg-text {
background-color: #FFB700;
color: #fff;
}
.msg-image {
max-width: 100%;
border-radius: 12rpx;
}
/* 系统消息居中 */
.msg-system-wrap {
display: flex;
justify-content: center;
margin-bottom: 20rpx;
}
.msg-system {
background-color: #f0f0f0;
padding: 10rpx 20rpx;
border-radius: 8rpx;
}
.msg-system text {
font-size: 24rpx;
color: #999;
}
/* 快捷操作栏 */
.quick-actions {
display: flex;
gap: 20rpx;
padding: 12rpx 24rpx;
flex-shrink: 0;
background: #f5f5f5;
}
.quick-btn {
padding: 10rpx 24rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
background: #fff;
}
.quick-btn text {
font-size: 22rpx;
color: #333;
}
/* 底部输入区域 */
.chat-bottom {
background: #fff;
border-top: 1rpx solid #eee;
flex-shrink: 0;
padding-bottom: env(safe-area-inset-bottom);
}
.footer-input-row {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
gap: 12rpx;
}
.chat-input {
flex: 1;
height: 72rpx;
background-color: #f5f5f5;
border-radius: 36rpx;
padding: 0 24rpx;
font-size: 28rpx;
}
.send-btn {
background: linear-gradient(135deg, #FFB700, #FF9500);
padding: 0 32rpx;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
flex-shrink: 0;
}
.send-btn text {
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
.plus-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: transform 0.2s;
}
.plus-btn.active {
transform: rotate(45deg);
}
.plus-icon {
width: 48rpx;
height: 48rpx;
}
/* 内嵌更多面板 */
.more-panel {
display: flex;
flex-wrap: wrap;
padding: 20rpx 10rpx 10rpx;
border-top: 1rpx solid #f0f0f0;
}
.panel-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 0;
}
.panel-icon-wrap {
width: 96rpx;
height: 96rpx;
background: #f5f5f5;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
}
.panel-icon-img {
width: 48rpx;
height: 48rpx;
}
.panel-label {
font-size: 22rpx;
color: #666;
}
/* 改价消息卡片 */
.price-change-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
width: 80%;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
}
.pc-header {
margin-bottom: 16rpx;
}
.pc-title {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.pc-body {
margin-bottom: 16rpx;
}
.pc-row {
display: flex;
justify-content: space-between;
padding: 8rpx 0;
}
.pc-label {
font-size: 24rpx;
color: #999;
}
.pc-value {
font-size: 26rpx;
color: #333;
}
.pc-new-price {
color: #FFB700;
font-weight: 500;
}
.pc-pay {
color: #ff6600;
}
.pc-refund {
color: #52c41a;
}
.pc-actions {
display: flex;
gap: 16rpx;
}
.pc-btn {
flex: 1;
padding: 16rpx 0;
border-radius: 8rpx;
text-align: center;
font-size: 26rpx;
}
.pc-accept {
background: #FFB700;
color: #fff;
}
.pc-reject {
background: #f5f5f5;
color: #666;
}
.pc-waiting {
text-align: center;
padding: 12rpx 0;
}
.pc-waiting text {
font-size: 24rpx;
color: #ff9900;
}
.pc-result {
text-align: center;
padding: 12rpx 0;
}
.pc-accepted {
font-size: 24rpx;
color: #52c41a;
}
.pc-rejected {
font-size: 24rpx;
color: #ff4d4f;
}
/* 改价弹窗 */
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
}
.popup-content {
width: 80%;
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
}
.popup-title {
font-size: 32rpx;
color: #333;
font-weight: 500;
display: block;
text-align: center;
margin-bottom: 30rpx;
}
.popup-field {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.popup-label {
font-size: 28rpx;
color: #666;
}
.popup-current-price {
font-size: 28rpx;
color: #333;
}
.popup-input {
width: 50%;
height: 64rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 0 16rpx;
font-size: 28rpx;
text-align: right;
}
.popup-diff {
padding: 16rpx 0;
text-align: center;
}
.popup-diff text {
font-size: 24rpx;
}
.popup-actions {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
}
.popup-btn {
flex: 1;
padding: 20rpx 0;
border-radius: 12rpx;
text-align: center;
font-size: 28rpx;
}
.popup-cancel {
background: #f5f5f5;
color: #666;
}
.popup-confirm {
background: #FAD146;
color: #fff;
}
</style>