669 lines
18 KiB
Plaintext
669 lines
18 KiB
Plaintext
<!-- 消费者端 - 订单详情页 -->
|
||
<template>
|
||
<view class="order-detail-page">
|
||
<scroll-view scroll-y="true" class="scroll-content">
|
||
<!-- 订单状态 -->
|
||
<view class="order-status" :class="getStatusClass()">
|
||
<view class="status-icon">
|
||
<text class="status-emoji">{{ getStatusIcon() }}</text>
|
||
</view>
|
||
<view class="status-info">
|
||
<text class="status-text">{{ getStatusText() }}</text>
|
||
<text class="status-desc">{{ getStatusDesc() }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 配送信息 -->
|
||
<view v-if="order.order_status >= 2" class="delivery-info card">
|
||
<view class="delivery-header">
|
||
<text class="section-title">配送信息</text>
|
||
</view>
|
||
<view class="delivery-address">
|
||
<view class="address-icon">📍</view>
|
||
<view class="address-content">
|
||
<view class="address-user">
|
||
<text class="recipient">{{ deliveryAddress.name }}</text>
|
||
<text class="phone">{{ deliveryAddress.phone }}</text>
|
||
</view>
|
||
<text class="address-detail">{{ getFullAddress(deliveryAddress) }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 如果有物流信息显示 -->
|
||
<view v-if="deliveryInfo.tracking_no" class="courier-info">
|
||
<text class="courier-label">物流单号:</text>
|
||
<text class="courier-value">{{ deliveryInfo.tracking_no }}</text>
|
||
<text class="copy-btn" @click="copyText(deliveryInfo.tracking_no)">复制</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品信息 -->
|
||
<view class="order-products card">
|
||
<view class="shop-header" @click="goToShop">
|
||
<text class="shop-icon">🏪</text>
|
||
<text class="shop-name">{{ shopName }}</text>
|
||
<text class="arrow-right">></text>
|
||
</view>
|
||
<view v-for="item in orderItems" :key="item.id" class="product-item" @click="goToProduct(item.product_id)">
|
||
<image :src="item.image_url || '/static/default-product.png'" class="product-image" mode="aspectFill"/>
|
||
<view class="product-info">
|
||
<text class="product-name">{{ item.product_name }}</text>
|
||
<text v-if="item.specifications" class="product-spec">{{ getSpecText(item.specifications) }}</text>
|
||
<view class="price-quantity">
|
||
<text class="product-price">¥{{ item.price }}</text>
|
||
<text class="product-quantity">×{{ item.quantity }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单信息 -->
|
||
<view class="order-info card">
|
||
<view class="info-row">
|
||
<text class="info-label">订单编号</text>
|
||
<text class="info-value copyable" @click="copyText(order.order_no)">{{ order.order_no }} <text class="copy-icon">📄</text></text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">下单时间</text>
|
||
<text class="info-value">{{ formatTime(order.created_at) }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order.payment_method">
|
||
<text class="info-label">支付方式</text>
|
||
<text class="info-value">{{ getPaymentMethodText(order.payment_method) }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order.paid_at">
|
||
<text class="info-label">支付时间</text>
|
||
<text class="info-value">{{ formatTime(order.paid_at) }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order.shipped_at">
|
||
<text class="info-label">发货时间</text>
|
||
<text class="info-value">{{ formatTime(order.shipped_at) }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order.completed_at">
|
||
<text class="info-label">完成时间</text>
|
||
<text class="info-value">{{ formatTime(order.completed_at) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 费用明细 -->
|
||
<view class="cost-detail card">
|
||
<view class="cost-row">
|
||
<text class="cost-label">商品总额</text>
|
||
<text class="cost-value">¥{{ order.product_amount }}</text>
|
||
</view>
|
||
<view class="cost-row">
|
||
<text class="cost-label">运费</text>
|
||
<text class="cost-value">+¥{{ order.shipping_fee || 0 }}</text>
|
||
</view>
|
||
<view class="cost-row" v-if="order.discount_amount > 0">
|
||
<text class="cost-label">优惠金额</text>
|
||
<text class="cost-value">-¥{{ order.discount_amount }}</text>
|
||
</view>
|
||
<view class="cost-row total">
|
||
<text class="cost-label">实付金额</text>
|
||
<text class="cost-value price">¥{{ order.total_amount }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部操作 -->
|
||
<view class="bottom-actions">
|
||
<view class="action-left">
|
||
<view class="service-btn" @click="contactService">
|
||
<text class="service-icon">🎧</text>
|
||
<text>客服</text>
|
||
</view>
|
||
</view>
|
||
<view class="action-right">
|
||
<button v-if="order.order_status === 1" class="btn primary" @click="payOrder">立即支付</button>
|
||
<button v-if="order.order_status === 1" class="btn" @click="cancelOrder">取消订单</button>
|
||
|
||
<button v-if="order.order_status === 2" class="btn" @click="remindDelivery">提醒发货</button>
|
||
<button v-if="order.order_status === 2" class="btn" @click="applyRefund">申请退款</button>
|
||
|
||
<button v-if="order.order_status === 3" class="btn primary" @click="confirmReceive">确认收货</button>
|
||
<button v-if="order.order_status === 3" class="btn" @click="viewLogistics">查看物流</button>
|
||
|
||
<button v-if="order.order_status === 4" class="btn primary" @click="goToReview">评价</button>
|
||
<button v-if="order.order_status === 4" class="btn" @click="rePurchase">再次购买</button>
|
||
<button v-if="order.order_status === 4" class="btn" @click="applyAfterSales">申请售后</button>
|
||
|
||
<button v-if="order.order_status === 5" class="btn" @click="rePurchase">重新购买</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
const orderId = ref('')
|
||
const order = ref<any>({})
|
||
const orderItems = ref<any[]>([])
|
||
const shopName = ref('店铺名称')
|
||
const deliveryAddress = ref<any>({})
|
||
const deliveryInfo = ref<any>({})
|
||
|
||
onLoad((options) => {
|
||
if (options['id']) {
|
||
orderId.value = options['id'] as string
|
||
loadOrderDetail()
|
||
} else if (options['orderId']) {
|
||
orderId.value = options['orderId'] as string
|
||
loadOrderDetail()
|
||
}
|
||
})
|
||
|
||
const loadOrderDetail = async () => {
|
||
uni.showLoading({ title: '加载中' })
|
||
try {
|
||
const data = await supabaseService.getOrderDetail(orderId.value)
|
||
if (data) {
|
||
order.value = data
|
||
orderItems.value = data.ml_order_items || []
|
||
deliveryAddress.value = data.shipping_address || {}
|
||
|
||
// 获取店铺信息
|
||
if (data.merchant_id) {
|
||
loadShopInfo(data.merchant_id)
|
||
}
|
||
} else {
|
||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
const loadShopInfo = async (merchantId: string) => {
|
||
const { data } = await supa
|
||
.from('ml_shops')
|
||
.select('shop_name')
|
||
.eq('merchant_id', merchantId)
|
||
.single()
|
||
if (data) {
|
||
shopName.value = data['shop_name'] as string
|
||
}
|
||
}
|
||
|
||
// 辅助函数
|
||
const getStatusText = () => {
|
||
const status = order.value.order_status
|
||
if (status == 1) return '待付款'
|
||
if (status == 2) return '待发货'
|
||
if (status == 3) return '待收货'
|
||
if (status == 4) return '已完成'
|
||
if (status == 5) return '已取消'
|
||
if (status == 6) return '退款中'
|
||
if (status == 7) return '已退款'
|
||
return '未知状态'
|
||
}
|
||
|
||
const getStatusDesc = () => {
|
||
const status = order.value.order_status
|
||
if (status == 1) return '请尽快完成支付'
|
||
if (status == 2) return '商家正在打包商品'
|
||
if (status == 3) return '商品正在赶往您的地址'
|
||
if (status == 4) return '订单已完成,感谢支持'
|
||
if (status == 5) return '订单已取消'
|
||
if (status == 6) return '售后处理中'
|
||
if (status == 7) return '钱款已退回'
|
||
return ''
|
||
}
|
||
|
||
const getStatusIcon = () => {
|
||
const status = order.value.order_status
|
||
if (status === 1) return '💳'
|
||
if (status === 2) return '📦'
|
||
if (status === 3) return '🚚'
|
||
if (status === 4) return '✅'
|
||
return '📝'
|
||
}
|
||
|
||
const getStatusClass = () => {
|
||
const status = order.value.order_status
|
||
return `status-${status}`
|
||
}
|
||
|
||
const getFullAddress = (addr: any) => {
|
||
if (!addr) return ''
|
||
// 兼容简单的字符串地址和对象地址
|
||
if (typeof addr === 'string') return addr
|
||
if (addr.address) return addr.address
|
||
return (addr.province || '') + (addr.city || '') + (addr.district || '') + (addr.detail || addr.address_detail || '')
|
||
}
|
||
|
||
const getSpecText = (specs: any) => {
|
||
if (!specs) return ''
|
||
if (typeof specs === 'string') return specs
|
||
return Object.keys(specs).map(k => `${k}:${specs[k]}`).join(' ')
|
||
}
|
||
|
||
const formatTime = (iso: string) => {
|
||
if (!iso) return ''
|
||
const d = new Date(iso)
|
||
return `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}`
|
||
}
|
||
|
||
const getPaymentMethodText = (method: any) => {
|
||
return '在线支付'
|
||
}
|
||
|
||
const copyText = (text: string) => {
|
||
if(!text) return
|
||
uni.setClipboardData({
|
||
data: text,
|
||
success: () => uni.showToast({ title: '已复制' })
|
||
})
|
||
}
|
||
|
||
// 动作函数
|
||
const contactService = () => {
|
||
uni.showActionSheet({
|
||
itemList: ['在线客服', '拨打电话'],
|
||
success: (res) => {
|
||
if (res.tapIndex === 1) {
|
||
// 模拟拨打电话
|
||
uni.makePhoneCall({ phoneNumber: '400-123-4567' })
|
||
} else {
|
||
uni.showToast({ title: '连接到了商家客服' })
|
||
// 这里可以跳转到聊天页面
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const payOrder = () => {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${order.value.total_amount}`
|
||
})
|
||
}
|
||
|
||
const cancelOrder = () => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要取消订单吗?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
const { error } = await supa.from('ml_orders').update({ order_status: 5 }).eq('id', orderId.value)
|
||
if(!error) {
|
||
order.value.order_status = 5
|
||
uni.showToast({ title: '订单已取消' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const remindDelivery = () => {
|
||
uni.showToast({ title: '已提醒商家尽快发货' })
|
||
}
|
||
|
||
const viewLogistics = () => {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/logistics?orderId=${orderId.value}` })
|
||
}
|
||
|
||
const confirmReceive = async () => {
|
||
uni.showModal({
|
||
title: '确认收货',
|
||
content: '确保您已收到货物',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
const result = await supabaseService.confirmReceipt(orderId.value)
|
||
if (result.success) {
|
||
order.value.order_status = 4
|
||
uni.showToast({ title: '收货成功' })
|
||
setTimeout(() => goToReview(), 1500)
|
||
} else {
|
||
uni.showToast({ title: result.error || '失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const goToReview = () => {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/review?orderId=${orderId.value}` })
|
||
}
|
||
|
||
const rePurchase = async () => {
|
||
uni.showLoading({ title: '处理中' })
|
||
const success = await supabaseService.rePurchase(order.value)
|
||
uni.hideLoading()
|
||
if (success) {
|
||
uni.showToast({ title: '已加入购物车' })
|
||
setTimeout(() => {
|
||
uni.switchTab({ url: '/pages/mall/consumer/cart' })
|
||
}, 1000)
|
||
} else {
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const applyRefund = () => {
|
||
uni.showModal({
|
||
title: '申请退款',
|
||
editable: true,
|
||
placeholderText: '请输入退款原因',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
const reason = res.content || '用户主动申请'
|
||
const success = await supabaseService.applyRefund(orderId.value, reason)
|
||
if (success) {
|
||
order.value.order_status = 6
|
||
uni.showToast({ title: '申请已提交' })
|
||
} else {
|
||
uni.showToast({ title: '提交失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const applyAfterSales = () => {
|
||
// 售后逻辑类似退款,或者是跳转到专门的售后单页面
|
||
applyRefund()
|
||
}
|
||
|
||
const goToShop = () => {
|
||
// 跳转到店铺详情
|
||
// uni.navigateTo({ url: `/pages/mall/shop/index?id=${order.value.merchant_id}` })
|
||
uni.showToast({ title: '进入店铺: ' + shopName.value })
|
||
}
|
||
|
||
const goToProduct = (pid: string) => {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${pid}` })
|
||
}
|
||
|
||
</script>
|
||
|
||
<style scoped>
|
||
.order-detail-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.scroll-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
.card {
|
||
background-color: #ffffff;
|
||
margin: 10px;
|
||
padding: 15px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* 状态栏 */
|
||
.order-status {
|
||
background: linear-gradient(to right, #ff9000, #ff5000);
|
||
padding: 20px 25px;
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.status-emoji {
|
||
font-size: 32px;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
display: block;
|
||
}
|
||
|
||
.status-desc {
|
||
font-size: 12px;
|
||
opacity: 0.9;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
|
||
/* 配送信息 */
|
||
.section-title {
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
margin-bottom: 10px;
|
||
display: block;
|
||
}
|
||
|
||
.delivery-address {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.address-icon {
|
||
font-size: 20px;
|
||
margin-right: 10px;
|
||
color: #666;
|
||
}
|
||
|
||
.address-user {
|
||
margin-bottom: 5px;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.phone {
|
||
margin-left: 10px;
|
||
color: #666;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.address-detail {
|
||
font-size: 13px;
|
||
color: #333;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.courier-info {
|
||
margin-top: 10px;
|
||
padding-top: 10px;
|
||
border-top: 1px solid #eee;
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.copy-btn {
|
||
margin-left: 10px;
|
||
color: #ff5000;
|
||
font-size: 12px;
|
||
border: 1px solid #ff5000;
|
||
padding: 1px 6px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* 店铺与商品 */
|
||
.shop-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
}
|
||
|
||
.shop-icon {
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.shop-name {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
flex: 1;
|
||
}
|
||
|
||
.arrow-right {
|
||
color: #999;
|
||
}
|
||
|
||
.product-item {
|
||
display: flex;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.product-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.product-image {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 6px;
|
||
margin-right: 10px;
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
.product-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.product-name {
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
color: #333;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.product-spec {
|
||
font-size: 12px;
|
||
color: #999;
|
||
background-color: #f5f5f5;
|
||
padding: 2px 5px;
|
||
border-radius: 4px;
|
||
align-self: flex-start;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.price-quantity {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.product-price {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.product-quantity {
|
||
color: #999;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 订单详情 */
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.info-label {
|
||
color: #999;
|
||
}
|
||
|
||
.info-value {
|
||
color: #333;
|
||
}
|
||
|
||
.copy-icon {
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 费用明细 */
|
||
.cost-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.cost-row.total {
|
||
margin-top: 15px;
|
||
padding-top: 15px;
|
||
border-top: 1px solid #f5f5f5;
|
||
align-items: center;
|
||
}
|
||
|
||
.cost-value.price {
|
||
color: #ff5000;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 底部按钮 */
|
||
.bottom-actions {
|
||
background-color: #ffffff;
|
||
padding: 10px 15px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||
padding-bottom: calc(10px + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.action-left {
|
||
display: flex;
|
||
}
|
||
|
||
.service-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
font-size: 10px;
|
||
color: #666;
|
||
background: none;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.service-icon {
|
||
font-size: 18px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.action-right {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.btn {
|
||
margin: 0;
|
||
padding: 0 15px;
|
||
height: 32px;
|
||
line-height: 30px;
|
||
font-size: 13px;
|
||
border-radius: 16px;
|
||
background: #ffffff;
|
||
border: 1px solid #cccccc;
|
||
color: #666;
|
||
}
|
||
|
||
.btn.primary {
|
||
border-color: #ff5000;
|
||
color: #ff5000;
|
||
background-color: #fff0ec;
|
||
}
|
||
|
||
/* 状态样式 */
|
||
.status-4 .status-text { /* Completed */ }
|
||
</style>
|