Files
solosw 99b11b04e4 1.0
2026-01-05 14:11:34 +08:00

837 lines
18 KiB
Vue
Raw Permalink 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="container">
<!-- 订单状态 -->
<view class="status-section">
<view class="status-icon" :class="statusClass">
<uni-icons :type="statusIcon" size="40" color="#fff"></uni-icons>
</view>
<view class="status-info">
<text class="status-text">{{ statusText }}</text>
<text class="status-desc">{{ statusDesc }}</text>
</view>
</view>
<!-- 订单信息 -->
<view class="order-section">
<view class="section-title">订单信息</view>
<view class="order-item">
<text class="item-label">订单号</text>
<text class="item-value">{{ orderInfo.order_no }}</text>
</view>
<view class="order-item">
<text class="item-label">订单类型</text>
<text class="item-value">{{ orderInfo.product_type === 1 ? '一级商品' : '二级商品' }}</text>
</view>
<view class="order-item">
<text class="item-label">下单时间</text>
<text class="item-value">{{ formatTime(orderInfo.created_at) }}</text>
</view>
<view class="order-item" v-if="orderInfo.confirm_time">
<text class="item-label">确认时间</text>
<text class="item-value">{{ formatTime(orderInfo.confirm_time) }}</text>
</view>
<view class="order-item" v-if="orderInfo.complete_time">
<text class="item-label">完成时间</text>
<text class="item-value">{{ formatTime(orderInfo.complete_time) }}</text>
</view>
</view>
<!-- 商品信息 -->
<view class="product-section">
<view class="section-title">商品信息</view>
<view class="product-card">
<image v-if="productImage" :src="getFullImageUrl(productImage)" class="product-image" mode="aspectFill"></image>
<view class="default-image" v-else>
<uni-icons type="image" size="40" color="#ccc"></uni-icons>
</view>
<view class="product-info">
<text class="product-name">{{ orderInfo.product_name }}</text>
<view class="product-price">
<text class="price-label">单价</text>
<text class="price-value">¥{{ orderInfo.unit_price||orderInfo.selling_price }}</text>
</view>
<view class="product-price">
<text class="price-label">订单金额</text>
<text class="price-value">¥{{ orderInfo.total_amount }}</text>
</view>
<view class="product-quantity">
<text class="quantity-label">数量</text>
<text class="quantity-value">{{ orderInfo.quantity || 1 }}</text>
</view>
</view>
</view>
</view>
<!-- 交易双方信息 -->
<view class="parties-section">
<view class="section-title">交易信息</view>
<view class="party-info" v-if="orderType === 'purchase'">
<text class="party-label">卖家</text>
<text class="party-value">{{ orderInfo.seller ? ( orderInfo.seller.real_name||orderInfo.seller.customer_name || orderInfo.seller.phone) : '暂无' }}</text>
</view>
<view class="party-info" v-if="orderType === 'sales'">
<text class="party-label">买家</text>
<text class="party-value">{{ orderInfo.buyer ? (orderInfo.buyer.real_name || orderInfo.buyer.customer_name || orderInfo.buyer.phone) : '暂无' }}</text>
</view>
<view class="party-info" v-if="orderInfo.address">
<text class="party-label">收货地址</text>
<text class="party-value">{{ formatAddress(orderInfo.address) }}</text>
</view>
</view>
<!-- 付款凭证 -->
<view class="proof-section" v-if="orderInfo.payment_proof_image">
<view class="section-title">付款凭证</view>
<view class="proof-image" @click="previewProof">
<image :src="getFullImageUrl(orderInfo.payment_proof_image)" mode="aspectFill"></image>
<view class="proof-overlay">
<uni-icons type="eye" size="30" color="#fff"></uni-icons>
<text class="proof-text">点击查看</text>
</view>
</view>
</view>
<!-- 留言区域 -->
<view class="message-section">
<view class="section-title">
<text>订单留言</text>
<text class="message-count">({{ messages.length }})</text>
</view>
<!-- 留言列表 -->
<view class="message-list" v-if="messages.length > 0">
<view class="message-item" v-for="(msg, index) in messages" :key="index">
<view class="message-header">
<text class="message-user">{{ msg.user.real_name || '匿名用户' }}</text>
<text class="message-time">{{ formatTime(msg.created_at) }}</text>
</view>
<view class="message-content">{{ msg.content }}</view>
<!-- 留言图片 -->
<view class="message-images" v-if="msg.images">
<image
v-for="(image, imgIndex) in getMessageImages(msg.images)"
:key="imgIndex"
:src="getFullImageUrl(image)"
class="message-image"
mode="aspectFill"
@click="previewMessageImage(msg.images, imgIndex)"
/>
</view>
</view>
</view>
<view class="no-message" v-else>
<uni-icons type="chatboxes" size="40" color="#ccc"></uni-icons>
<text>暂无留言</text>
</view>
<!-- 发送留言 -->
<view class="send-message">
<view class="message-input-container">
<textarea
v-model="newMessage"
class="message-input"
placeholder="请输入留言内容..."
maxlength="500"
auto-height
></textarea>
<text class="char-count">{{ newMessage.length }}/500</text>
</view>
<!-- 图片选择和预览 -->
<view class="image-section" v-if="selectedImages.length > 0">
<view class="selected-images">
<view class="image-item" v-for="(image, index) in selectedImages" :key="index">
<image :src="image" class="selected-image" mode="aspectFill" />
<view class="remove-image" @click="removeImage(index)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
</view>
</view>
</view>
<view class="send-actions">
<view class="action-buttons">
<button class="image-btn" @click="chooseImage" :disabled="selectedImages.length >= 6">
<uni-icons type="camera" size="20" color="#666"></uni-icons>
<text class="btn-text">图片({{ selectedImages.length }}/6)</text>
</button>
</view>
<button class="send-btn" @click="sendMessage" :disabled="(!newMessage.trim() && selectedImages.length === 0) || sending">
<text v-if="sending">发送中...</text>
<text v-else>发送</text>
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import request from '@/utils/request.js'
export default {
data() {
return {
orderType: 'purchase', // purchase: 买单, sales: 卖单
orderId: '',
orderInfo: {},
messages: [],
newMessage: '',
selectedImages: [], // 选中的图片列表
sending: false,
loading: true
}
},
computed: {
statusClass() {
const statusMap = {
0: 'status-pending', // 待付款
1: 'status-paid', // 已付款
2: 'status-completed', // 已完成
3: 'status-cancelled' // 已取消
}
return statusMap[this.orderInfo.order_status] || 'status-pending'
},
statusIcon() {
const iconMap = {
0: 'wallet', // 待付款
1: 'checkmarkempty', // 已付款
2: 'checkbox', // 已完成
3: 'closeempty' // 已取消
}
return iconMap[this.orderInfo.order_status] || 'wallet'
},
statusText() {
const textMap = {
0: '待付款',
1: '已付款',
2: '已完成',
3: '已取消'
}
return textMap[this.orderInfo.order_status] || '未知状态'
},
statusDesc() {
const descMap = {
0: '请及时完成付款',
1: '等待卖家确认',
2: '交易已完成',
3: '订单已取消'
}
return descMap[this.orderInfo.order_status] || ''
},
productImage() {
if (this.orderInfo.product_images) {
const images = this.orderInfo.product_images
if (typeof images === 'string') {
return images.split(',')[0].trim()
}
if (Array.isArray(images) && images.length > 0) {
return images[0]
}
}
if (this.orderInfo.secondary_product?.primary_product?.product_images) {
const images = this.orderInfo.secondary_product.primary_product.product_images
if (typeof images === 'string') {
return images.split(',')[0].trim()
}
if (Array.isArray(images) && images.length > 0) {
return images[0]
}
}
return ''
},
productName() {
return this.orderInfo.primary_product?.product_name ||
this.orderInfo.secondary_product?.primary_product?.product_name ||
'未知商品'
},
productDesc() {
return this.orderInfo.primary_product?.product_description ||
this.orderInfo.secondary_product?.primary_product?.product_description ||
'暂无描述'
}
},
onLoad(options) {
// 检查登录状态
const token = uni.getStorageSync('token')
if (!token) {
uni.reLaunch({
url: '/pages/login/login'
})
return
}
this.orderType = options.type || 'purchase' // purchase 或 sales
this.orderId = options.id
if (!this.orderId) {
uni.showToast({
title: '订单ID不能为空',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
return
}
this.loadOrderDetail()
this.loadMessages()
},
methods: {
// 加载订单详情
async loadOrderDetail() {
this.loading = true
try {
const url = this.orderType === 'purchase'
? `/back/user/orders/purchase/${this.orderId}`
: `/back/user/orders/sales/${this.orderId}`
const res = await request.get(url)
if (res.success) {
this.orderInfo = res.data
} else {
throw new Error(res.message || '加载订单详情失败')
}
} catch (error) {
console.error('加载订单详情失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
// 加载留言列表
async loadMessages() {
try {
const res = await request.get(`/back/user/orders/${this.orderId}/messages`,{type:this.orderType})
if (res.success) {
this.messages = res.data.list || []
}
} catch (error) {
console.error('加载留言失败:', error)
}
},
// 发送留言
async sendMessage() {
if (!this.newMessage.trim() && this.selectedImages.length === 0) {
uni.showToast({
title: '请输入留言内容或选择图片',
icon: 'none'
})
return
}
this.sending = true
try {
// 上传图片并获取URL
let imageUrls = []
if (this.selectedImages.length > 0) {
for (let image of this.selectedImages) {
const uploadRes = await request.upload('/back/upload', image)
if (uploadRes.success) {
imageUrls.push(uploadRes.data.url)
}
}
}
const res = await request.post(`/back/user/orders/${this.orderId}/messages`, {
content: this.newMessage.trim(),
images: imageUrls.join(',')
})
if (res.success) {
this.newMessage = ''
this.selectedImages = []
this.loadMessages() // 重新加载留言列表
uni.showToast({
title: '发送成功',
icon: 'success'
})
} else {
throw new Error(res.message || '发送失败')
}
} catch (error) {
console.error('发送留言失败:', error)
uni.showToast({
title: '发送失败',
icon: 'none'
})
} finally {
this.sending = false
}
},
// 预览付款凭证
previewProof() {
uni.previewImage({
urls: [this.getFullImageUrl(this.orderInfo.payment_proof_image)],
current: 0
})
},
// 获取完整图片URL
getFullImageUrl(url) {
return request.getImageUrl(url)
},
// 格式化时间
formatTime(time) {
if (!time) return '暂无'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
},
// 格式化地址
formatAddress(address) {
if (!address) return '暂无'
return `${address.province || ''} ${address.city || ''} ${address.district || ''} ${address.detail_address || ''}`
},
// 选择图片
chooseImage() {
const remaining = 6 - this.selectedImages.length
if (remaining <= 0) {
uni.showToast({
title: '最多只能选择6张图片',
icon: 'none'
})
return
}
uni.chooseImage({
count: remaining,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.selectedImages = this.selectedImages.concat(res.tempFilePaths)
}
})
},
// 删除图片
removeImage(index) {
this.selectedImages.splice(index, 1)
},
// 获取留言图片列表
getMessageImages(images) {
if (!images) return []
return images.split(',').filter(img => img.trim())
},
// 预览留言图片
previewMessageImage(images, currentIndex) {
const imageList = this.getMessageImages(images)
const urls = imageList.map(img => this.getFullImageUrl(img))
uni.previewImage({
urls: urls,
current: currentIndex
})
}
}
}
</script>
<style scoped>
.container {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 20rpx;
}
/* 状态区域 */
.status-section {
background: linear-gradient(135deg, #FF4444 0%, #FF6666 100%);
padding: 40rpx 30rpx;
display: flex;
align-items: center;
}
.status-icon {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
margin-right: 30rpx;
}
.status-info {
flex: 1;
}
.status-text {
display: block;
font-size: 36rpx;
font-weight: bold;
color: #fff;
margin-bottom: 10rpx;
}
.status-desc {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 通用section样式 */
.order-section, .product-section, .parties-section, .proof-section, .message-section {
background-color: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.message-count {
font-size: 24rpx;
color: #999;
font-weight: normal;
}
/* 订单信息 */
.order-item, .party-info {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.order-item:last-child, .party-info:last-child {
margin-bottom: 0;
}
.item-label, .party-label {
width: 200rpx;
font-size: 28rpx;
color: #666;
flex-shrink: 0;
}
.item-value, .party-value {
flex: 1;
font-size: 28rpx;
color: #333;
}
/* 商品卡片 */
.product-card {
display: flex;
align-items: flex-start;
}
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 20rpx;
}
.default-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.product-info {
flex: 1;
}
.product-name {
display: block;
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
line-height: 1.4;
}
.product-desc {
display: block;
font-size: 24rpx;
color: #666;
margin-bottom: 15rpx;
line-height: 1.4;
}
.product-price, .product-quantity {
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.price-label, .quantity-label {
font-size: 26rpx;
color: #666;
margin-right: 10rpx;
}
.price-value {
font-size: 30rpx;
font-weight: bold;
color: #FF4444;
}
.quantity-value {
font-size: 26rpx;
color: #333;
}
/* 付款凭证 */
.proof-image {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
overflow: hidden;
}
.proof-image image {
width: 100%;
height: 100%;
}
.proof-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.proof-text {
font-size: 24rpx;
color: #fff;
margin-top: 10rpx;
}
/* 留言区域 */
.message-list {
margin-bottom: 30rpx;
}
.message-item {
border-bottom: 1rpx solid #f0f0f0;
padding-bottom: 20rpx;
margin-bottom: 20rpx;
}
.message-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
}
.message-user {
font-size: 26rpx;
color: #666;
font-weight: bold;
}
.message-time {
font-size: 22rpx;
color: #999;
}
.message-content {
font-size: 28rpx;
color: #333;
line-height: 1.5;
}
.no-message {
text-align: center;
padding: 60rpx 0;
color: #999;
}
.no-message text {
display: block;
margin-top: 20rpx;
font-size: 26rpx;
}
/* 留言图片 */
.message-images {
margin-top: 15rpx;
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.message-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
}
/* 发送留言 */
.send-message {
border-top: 1rpx solid #f0f0f0;
padding-top: 30rpx;
}
.message-input-container {
position: relative;
margin-bottom: 20rpx;
}
.message-input {
width: 100%;
min-height: 120rpx;
padding: 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
background-color: #f8f8f8;
}
.char-count {
position: absolute;
right: 20rpx;
bottom: 10rpx;
font-size: 22rpx;
color: #999;
}
/* 图片选择区域 */
.image-section {
margin-bottom: 20rpx;
}
.selected-images {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.image-item {
position: relative;
width: 100rpx;
height: 100rpx;
}
.selected-image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.remove-image {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 32rpx;
height: 32rpx;
background-color: #ff4444;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 发送操作区域 */
.send-actions {
display: flex;
align-items: center;
gap: 20rpx;
}
.action-buttons {
flex: 1;
}
.image-btn {
background: transparent;
border: 1rpx solid #e0e0e0;
border-radius: 8rpx;
padding: 10rpx 20rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
font-size: 24rpx;
color: #666;
}
.image-btn:disabled {
color: #ccc;
border-color: #f0f0f0;
}
.btn-text {
font-size: 24rpx;
}
.send-btn {
width: 120rpx;
height: 60rpx;
background: linear-gradient(135deg, #FF4444 0%, #FF6666 100%);
border: none;
border-radius: 30rpx;
color: #fff;
font-size: 26rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.send-btn:disabled {
background: #ccc;
}
/* 状态颜色 */
.status-pending {
background-color: rgba(255, 193, 7, 0.2) !important;
}
.status-paid {
background-color: rgba(23, 162, 184, 0.2) !important;
}
.status-completed {
background-color: rgba(40, 167, 69, 0.2) !important;
}
.status-cancelled {
background-color: rgba(220, 53, 69, 0.2) !important;
}
</style>