1345 lines
37 KiB
Plaintext
1345 lines
37 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-content">
|
||
<view class="status-info">
|
||
<view class="status-title-row">
|
||
<text class="status-emoji">{{ getStatusIcon() }}</text>
|
||
<text class="status-text">{{ getStatusText() }}</text>
|
||
</view>
|
||
<text class="status-desc">{{ getStatusDesc() }}</text>
|
||
</view>
|
||
<!-- 分享免单入口 -->
|
||
<view v-if="order?.order_status === 4" class="share-free-entry" @click="shareForFree">
|
||
<text class="share-free-icon">🎁</text>
|
||
<view class="share-free-info">
|
||
<text class="share-free-title">分享免单</text>
|
||
<text class="share-free-desc">分享给好友,4人购买即可免单</text>
|
||
</view>
|
||
<text class="share-free-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 配送信息 -->
|
||
<view v-if="order != null && (order?.order_status ?? 0) >= 2" class="delivery-info card">
|
||
<view class="delivery-header">
|
||
<text class="section-title">配送信息</text>
|
||
</view>
|
||
<view class="delivery-info-content">
|
||
<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 as any) }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 如果有物流信息显示 -->
|
||
<view v-if="deliveryInfo != null && deliveryInfo?.tracking_no != ''" class="courier-info">
|
||
<view class="courier-icon">🚚</view>
|
||
<view class="courier-content">
|
||
<text class="courier-label">物流信息</text>
|
||
<view class="tracking-row">
|
||
<text class="carrier-name">{{ deliveryInfo?.carrier_name ?? '快递运单' }}</text>
|
||
<text class="tracking-no">{{ deliveryInfo?.tracking_no ?? '' }}</text>
|
||
<view class="copy-tag" @click="copyText(deliveryInfo?.tracking_no ?? '')">
|
||
<text class="copy-tag-text">复制</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</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 != null && item.image_url != '' ? 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" v-if="order != null">
|
||
<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 != null && order?.payment_method != ''">
|
||
<text class="info-label">支付方式</text>
|
||
<text class="info-value">{{ getPaymentMethodText(order?.payment_method as any) }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="order?.paid_at != null && 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 != null && 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 != null && order?.completed_at != ''">
|
||
<text class="info-label">完成时间</text>
|
||
<text class="info-value">{{ formatTime(order?.completed_at ?? '') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 费用明细 -->
|
||
<view class="cost-detail card" v-if="order != null">
|
||
<view class="cost-row">
|
||
<text class="cost-label">商品总额</text>
|
||
<text class="cost-value">¥{{ order?.product_amount ?? 0 }}</text>
|
||
</view>
|
||
<view class="cost-row">
|
||
<text class="cost-label">运费</text>
|
||
<text class="cost-value">+¥{{ order?.shipping_fee != null ? order?.shipping_fee : 0 }}</text>
|
||
</view>
|
||
<view class="cost-row" v-if="(order?.discount_amount ?? 0) > 0">
|
||
<text class="cost-label">优惠金额</text>
|
||
<text class="cost-value">-¥{{ order?.discount_amount ?? 0 }}</text>
|
||
</view>
|
||
<view class="cost-row total">
|
||
<text class="cost-label">实付金额</text>
|
||
<text class="cost-value price">¥{{ order?.total_amount ?? 0 }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="bottom-actions" v-if="order != null">
|
||
<view class="action-bar-wrapper">
|
||
<view class="action-left" @click="contactService">
|
||
<view class="service-item">
|
||
<text class="service-icon">🎧</text>
|
||
<text class="service-label">客服</text>
|
||
</view>
|
||
</view>
|
||
<view class="action-right">
|
||
<view v-if="order?.order_status === 1" class="btn-group">
|
||
<button class="btn" @click="cancelOrder">取消订单</button>
|
||
<button class="btn primary" @click="payOrder">立即支付</button>
|
||
</view>
|
||
|
||
<view v-if="order?.order_status === 2" class="btn-group">
|
||
<button class="btn" @click="applyRefund">申请退款</button>
|
||
<button class="btn primary" @click="remindDelivery">提醒发货</button>
|
||
</view>
|
||
|
||
<view v-if="order?.order_status === 3" class="btn-group">
|
||
<button class="btn" @click="viewLogistics">查看物流</button>
|
||
<button class="btn primary" @click="confirmReceive">确认收货</button>
|
||
</view>
|
||
|
||
<view v-if="order?.order_status === 4" class="btn-group">
|
||
<button class="btn" @click="applyAfterSales">申请售后</button>
|
||
<button class="btn share-free" @click="shareForFree">分享免单</button>
|
||
<button class="btn" @click="rePurchase">再次购买</button>
|
||
<button class="btn primary" @click="goToReview">评价订单</button>
|
||
</view>
|
||
|
||
<view v-if="order?.order_status === 5" class="btn-group">
|
||
<button class="btn primary" @click="rePurchase">重新购买</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
// 定义订单类型
|
||
type OrderType = {
|
||
order_no: string,
|
||
order_status: number,
|
||
total_amount: number,
|
||
product_amount: number,
|
||
shipping_fee: number,
|
||
discount_amount: number,
|
||
payment_method: string,
|
||
created_at: string,
|
||
paid_at: string,
|
||
shipped_at: string,
|
||
completed_at: string,
|
||
merchant_id: string,
|
||
shipping_address: any | null
|
||
}
|
||
|
||
type OrderItemType = {
|
||
id: string,
|
||
product_id: string,
|
||
product_name: string,
|
||
image_url: string,
|
||
price: number,
|
||
quantity: number,
|
||
specifications: any
|
||
}
|
||
|
||
type AddressType = {
|
||
name: string,
|
||
phone: string,
|
||
province: string,
|
||
city: string,
|
||
district: string,
|
||
detail: string,
|
||
address: string
|
||
}
|
||
|
||
type DeliveryInfoType = {
|
||
tracking_no: string,
|
||
carrier_name: string
|
||
}
|
||
|
||
const orderId = ref('')
|
||
const order = ref<OrderType | null>(null)
|
||
const orderItems = ref<OrderItemType[]>([])
|
||
const shopName = ref('店铺名称')
|
||
const deliveryAddress = ref<AddressType | null>(null)
|
||
const deliveryInfo = ref<DeliveryInfoType | null>(null)
|
||
|
||
// 辅助函数 - 必须在调用前定义
|
||
const getStatusText = (): string => {
|
||
const status = order.value?.order_status ?? 0
|
||
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 = (): string => {
|
||
const status = order.value?.order_status ?? 0
|
||
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 = (): string => {
|
||
const status = order.value?.order_status ?? 0
|
||
if (status === 1) return '💳'
|
||
if (status === 2) return '📦'
|
||
if (status === 3) return '🚚'
|
||
if (status === 4) return '✅'
|
||
return '📝'
|
||
}
|
||
|
||
const getStatusClass = (): string => {
|
||
const status = order.value?.order_status ?? 0
|
||
return `status-${status}`
|
||
}
|
||
|
||
const getFullAddress = (addr: any): string => {
|
||
if (addr == null) return ''
|
||
if (typeof addr === 'string') return addr
|
||
|
||
try {
|
||
const addrObj = JSON.parse(JSON.stringify(addr)) as UTSJSONObject
|
||
const addressField = addrObj.getString('address')
|
||
if (addressField != null && addressField != '') return addressField
|
||
|
||
const province = addrObj.getString('province') ?? ''
|
||
const city = addrObj.getString('city') ?? ''
|
||
const district = addrObj.getString('district') ?? ''
|
||
const detail = addrObj.getString('detail') ?? addrObj.getString('address_detail') ?? ''
|
||
return province + city + district + detail
|
||
} catch (e) {
|
||
console.error('[getFullAddress] 解析地址失败:', e)
|
||
return ''
|
||
}
|
||
}
|
||
|
||
function formatSpecs(specs: any): string {
|
||
if (specs == null) return ''
|
||
if (typeof specs === 'string') {
|
||
if (specs == '') return ''
|
||
try {
|
||
const parsed = JSON.parse(specs as string)
|
||
if (parsed != null) {
|
||
return formatSpecs(parsed)
|
||
}
|
||
return specs as string
|
||
} catch (e) {
|
||
return specs as string
|
||
}
|
||
}
|
||
|
||
try {
|
||
const specStr = JSON.stringify(specs)
|
||
const specObj = JSON.parse(specStr) as UTSJSONObject
|
||
|
||
// 定义常见的键名
|
||
const keys = ['Color', 'Size', '颜色', '尺寸', '规格', '默认', 'spec', 'color', 'size']
|
||
const parts : string[] = []
|
||
|
||
// 尝试提取键值
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const key = keys[i]
|
||
const val = specObj.get(key)
|
||
if (val != null && val != '') {
|
||
parts.push(val.toString())
|
||
}
|
||
}
|
||
|
||
// 如果提取到了内容
|
||
if (parts.length > 0) {
|
||
return parts.join(' | ')
|
||
}
|
||
|
||
// 如果没有提取到已知键,则进行通用清理
|
||
return specStr.replace(/[{}"]/g, '').replace(/:/g, ': ').replace(/,/g, ' | ')
|
||
} catch (e) {
|
||
return ''
|
||
}
|
||
}
|
||
|
||
const getSpecText = (specs: any): string => {
|
||
return formatSpecs(specs)
|
||
}
|
||
|
||
const formatTime = (iso: string): 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): string => {
|
||
return '在线支付'
|
||
}
|
||
|
||
const copyText = (text: string) => {
|
||
if(text == '') return
|
||
uni.setClipboardData({
|
||
data: text,
|
||
success: () => uni.showToast({ title: '已复制' })
|
||
})
|
||
}
|
||
|
||
const loadShopInfo = async (merchantId: string) => {
|
||
try {
|
||
const result = await supa
|
||
.from('ml_shops')
|
||
.select('shop_name')
|
||
.eq('merchant_id', merchantId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (result.error != null) {
|
||
console.error('[loadShopInfo] 获取店铺信息失败:', result.error)
|
||
return
|
||
}
|
||
|
||
const rawData = result.data
|
||
if (rawData == null) return
|
||
|
||
const rawList = rawData as any[]
|
||
if (rawList.length == 0) return
|
||
|
||
const shopData = rawList[0]
|
||
const shopObj = JSON.parse(JSON.stringify(shopData)) as UTSJSONObject
|
||
shopName.value = (shopObj.getString('shop_name') ?? '店铺') as string
|
||
} catch (e) {
|
||
console.error('[loadShopInfo] 获取店铺信息异常:', e)
|
||
}
|
||
}
|
||
|
||
const loadOrderDetail = async () => {
|
||
uni.showLoading({ title: '加载中' })
|
||
try {
|
||
const data = await supabaseService.getOrderDetail(orderId.value)
|
||
console.log('[loadOrderDetail] 获取到的数据:', JSON.stringify(data))
|
||
|
||
if (data != null) {
|
||
const dataObj = data as UTSJSONObject
|
||
|
||
order.value = {
|
||
order_no: (dataObj.get('order_no') ?? '') as string,
|
||
order_status: (dataObj.get('order_status') ?? 1) as number,
|
||
total_amount: (dataObj.get('total_amount') ?? 0) as number,
|
||
product_amount: (dataObj.get('product_amount') ?? 0) as number,
|
||
shipping_fee: (dataObj.get('shipping_fee') ?? 0) as number,
|
||
discount_amount: (dataObj.get('discount_amount') ?? 0) as number,
|
||
payment_method: (dataObj.get('payment_method') ?? '') as string,
|
||
created_at: (dataObj.get('created_at') ?? '') as string,
|
||
paid_at: (dataObj.get('paid_at') ?? '') as string,
|
||
shipped_at: (dataObj.get('shipped_at') ?? '') as string,
|
||
completed_at: (dataObj.get('completed_at') ?? '') as string,
|
||
merchant_id: (dataObj.get('merchant_id') ?? '') as string,
|
||
shipping_address: (dataObj.get('shipping_address') ?? null) as any
|
||
} as OrderType
|
||
|
||
const itemsRaw = dataObj.get('ml_order_items')
|
||
console.log('[loadOrderDetail] 订单商品数据:', itemsRaw)
|
||
|
||
if (itemsRaw != null && Array.isArray(itemsRaw)) {
|
||
const items = itemsRaw as any[]
|
||
orderItems.value = []
|
||
for (let i = 0; i < items.length; i++) {
|
||
const item = items[i]
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const orderItem: OrderItemType = {
|
||
id: (itemObj.get('id') ?? '') as string,
|
||
product_id: (itemObj.get('product_id') ?? '') as string,
|
||
product_name: (itemObj.get('product_name') ?? '未知商品') as string,
|
||
price: (itemObj.get('price') ?? 0) as number,
|
||
quantity: (itemObj.get('quantity') ?? 1) as number,
|
||
image_url: (itemObj.get('image_url') ?? '') as string,
|
||
specifications: (itemObj.get('specifications') ?? '') as any
|
||
}
|
||
orderItems.value.push(orderItem)
|
||
}
|
||
}
|
||
|
||
const addressRaw = dataObj.get('shipping_address')
|
||
console.log('[loadOrderDetail] 收货地址数据:', addressRaw)
|
||
|
||
if (addressRaw != null) {
|
||
let addressObj: UTSJSONObject
|
||
if (addressRaw instanceof UTSJSONObject) {
|
||
addressObj = addressRaw as UTSJSONObject
|
||
} else if (typeof addressRaw === 'string') {
|
||
addressObj = JSON.parse(addressRaw as string) as UTSJSONObject
|
||
} else {
|
||
addressObj = JSON.parse(JSON.stringify(addressRaw)) as UTSJSONObject
|
||
}
|
||
|
||
const province = (addressObj.get('province') ?? '') as string
|
||
const city = (addressObj.get('city') ?? '') as string
|
||
const district = (addressObj.get('district') ?? '') as string
|
||
const detail = (addressObj.get('detail') ?? (addressObj.get('address_detail') ?? '')) as string
|
||
|
||
deliveryAddress.value = {
|
||
name: (addressObj.get('name') ?? (addressObj.get('recipient_name') ?? (addressObj.get('receiver_name') ?? ''))) as string,
|
||
phone: (addressObj.get('phone') ?? (addressObj.get('recipient_phone') ?? (addressObj.get('receiver_phone') ?? ''))) as string,
|
||
province: province,
|
||
city: city,
|
||
district: district,
|
||
detail: detail,
|
||
address: province + city + district + detail
|
||
} as AddressType
|
||
}
|
||
|
||
const merchantId = (dataObj.get('merchant_id') ?? '') as string
|
||
if (merchantId != '') {
|
||
loadShopInfo(merchantId)
|
||
}
|
||
|
||
// 加载物流信息
|
||
const trackingNoVal = dataObj.getString('tracking_no')
|
||
const carrierNameVal = dataObj.getString('carrier_name')
|
||
if (trackingNoVal != null && trackingNoVal != '') {
|
||
deliveryInfo.value = {
|
||
tracking_no: trackingNoVal,
|
||
carrier_name: carrierNameVal ?? ''
|
||
} as DeliveryInfoType
|
||
}
|
||
|
||
console.log('[loadOrderDetail] 订单详情加载成功,商品数量:', orderItems.value.length)
|
||
} else {
|
||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('[loadOrderDetail] 加载订单详情失败:', e)
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
|
||
// 动作函数
|
||
const contactService = () => {
|
||
if (order.value != null && order.value?.merchant_id != '') {
|
||
// 跳转到商家的聊天窗口
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/chat?merchantId=${order.value?.merchant_id}&merchantName=${encodeURIComponent(shopName.value)}`
|
||
})
|
||
} else {
|
||
uni.showActionSheet({
|
||
itemList: ['在线客服', '拨打电话'],
|
||
success: (res) => {
|
||
if (res.tapIndex === 1) {
|
||
// 模拟拨打电话
|
||
uni.makePhoneCall({ phoneNumber: '400-123-4567' })
|
||
} else {
|
||
uni.showToast({ title: '连接到了系统客服' })
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
const payOrder = () => {
|
||
const totalAmount = order.value?.total_amount ?? 0
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`
|
||
})
|
||
}
|
||
|
||
const doCancelOrder = async () => {
|
||
try {
|
||
const updatePayload = new UTSJSONObject()
|
||
updatePayload.set('order_status', 5)
|
||
updatePayload.set('updated_at', new Date().toISOString())
|
||
|
||
const result = await supa
|
||
.from('ml_orders')
|
||
.update(updatePayload)
|
||
.eq('id', orderId.value)
|
||
.execute()
|
||
|
||
if (result.error == null) {
|
||
if (order.value != null) {
|
||
order.value.order_status = 5
|
||
}
|
||
uni.showToast({ title: '订单已取消' })
|
||
} else {
|
||
console.error('[doCancelOrder] 取消订单失败:', result.error)
|
||
uni.showToast({ title: '取消失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('[doCancelOrder] 取消订单异常:', e)
|
||
uni.showToast({ title: '取消失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const cancelOrder = () => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要取消订单吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
doCancelOrder()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const remindDelivery = async () => {
|
||
const merchantId = order.value?.merchant_id
|
||
if (merchantId == null || merchantId == '') {
|
||
uni.showToast({ title: '商家信息不存在', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const orderNo = order.value?.order_no ?? ''
|
||
const message = `您好,订单 ${orderNo} 已付款,请尽快安排发货,谢谢!`
|
||
|
||
uni.showLoading({ title: '发送中...' })
|
||
const success = await supabaseService.sendChatMessage(message, merchantId, 'text')
|
||
uni.hideLoading()
|
||
|
||
if (success) {
|
||
uni.showToast({ title: '已提醒商家尽快发货' })
|
||
} else {
|
||
uni.showToast({ title: '发送失败,请稍后重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const viewLogistics = () => {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/logistics?orderId=${orderId.value}` })
|
||
}
|
||
|
||
const goToReview = () => {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/review?orderId=${orderId.value}` })
|
||
}
|
||
|
||
const doConfirmReceive = async () => {
|
||
try {
|
||
const result = await supabaseService.confirmReceipt(orderId.value)
|
||
if (result.success) {
|
||
if (order.value != null) {
|
||
order.value.order_status = 4
|
||
}
|
||
uni.showToast({ title: '收货成功' })
|
||
setTimeout(() => goToReview(), 1500)
|
||
} else {
|
||
uni.showToast({ title: result.error ?? '失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('[doConfirmReceive] 确认收货异常:', e)
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const confirmReceive = () => {
|
||
uni.showModal({
|
||
title: '确认收货',
|
||
content: '确保您已收到货物',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
doConfirmReceive()
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const rePurchase = async () => {
|
||
uni.showLoading({ title: '处理中' })
|
||
try {
|
||
const items = orderItems.value
|
||
if (items.length == 0) {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '没有可购买的商品', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
let successCount = 0
|
||
for (let i = 0; i < items.length; i++) {
|
||
const item = items[i]
|
||
const result = await supabaseService.addToCart(
|
||
item.product_id,
|
||
item.quantity,
|
||
'',
|
||
order.value?.merchant_id ?? ''
|
||
)
|
||
if (result) successCount++
|
||
}
|
||
|
||
uni.hideLoading()
|
||
|
||
if (successCount > 0) {
|
||
uni.showToast({ title: '已加入购物车' })
|
||
setTimeout(() => {
|
||
uni.switchTab({ url: '/pages/main/cart' })
|
||
}, 1000)
|
||
} else {
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
console.error('[rePurchase] 再次购买异常:', e)
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const doApplyRefund = async (reason: string) => {
|
||
try {
|
||
const success = await supabaseService.applyRefund(orderId.value, reason)
|
||
if (success) {
|
||
if (order.value != null) {
|
||
order.value.order_status = 6
|
||
}
|
||
uni.showToast({ title: '申请已提交' })
|
||
} else {
|
||
uni.showToast({ title: '提交失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('[doApplyRefund] 申请退款异常:', e)
|
||
uni.showToast({ title: '提交失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const applyRefund = () => {
|
||
uni.showModal({
|
||
title: '申请退款',
|
||
editable: true,
|
||
placeholderText: '请输入退款原因',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
const reason = res.content ?? '用户主动申请'
|
||
doApplyRefund(reason)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
const applyAfterSales = () => {
|
||
// 售后逻辑类似退款,或者是跳转到专门的售后单页面
|
||
applyRefund()
|
||
}
|
||
|
||
const goToShop = () => {
|
||
// 跳转到店铺详情
|
||
const merchantId = order.value?.merchant_id ?? ''
|
||
if (merchantId != '') {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/shop-detail?id=${merchantId}`
|
||
})
|
||
} else {
|
||
uni.showToast({ title: '商家信息不存在', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const goToProduct = (pid: string) => {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${pid}` })
|
||
}
|
||
|
||
const shareForFree = async () => {
|
||
if (orderItems.value.length === 0) {
|
||
uni.showToast({ title: '没有可分享的商品', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const firstItem = orderItems.value[0]
|
||
|
||
try {
|
||
uni.showLoading({ title: '创建分享...' })
|
||
const result = await supabaseService.createShareRecord(
|
||
firstItem.product_id,
|
||
orderId.value,
|
||
firstItem.id,
|
||
firstItem.product_name,
|
||
firstItem.image_url,
|
||
firstItem.price
|
||
)
|
||
uni.hideLoading()
|
||
|
||
const shareIdRaw = result.get('id')
|
||
const shareCodeRaw = result.get('share_code')
|
||
|
||
if (shareIdRaw != null && shareCodeRaw != null) {
|
||
const shareId = shareIdRaw as string
|
||
const shareCode = shareCodeRaw as string
|
||
|
||
uni.showModal({
|
||
title: '分享成功',
|
||
content: `您的分享码: ${shareCode}\n分享给好友,当有4人购买后即可免单!`,
|
||
confirmText: '查看详情',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.navigateTo({ url: `/pages/mall/consumer/share/detail?id=${shareId}` })
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
uni.showToast({ title: '分享创建失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
console.error('[shareForFree] 创建分享失败:', e)
|
||
uni.showToast({ title: '分享失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
// 使用 onBackPress 拦截物理返回键和系统导航栏返回
|
||
onBackPress((_): boolean => {
|
||
const pages = getCurrentPages()
|
||
console.log('[order-detail onBackPress] pages count:', pages.length)
|
||
|
||
if (pages.length > 1) {
|
||
// 正常返回上一页
|
||
return false
|
||
}
|
||
|
||
// 如果只有当前页面,跳转到 orders
|
||
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
|
||
return true
|
||
})
|
||
|
||
// 生命周期 - 在所有函数定义之后
|
||
onLoad((options) => {
|
||
const id = options['id']
|
||
const orderIdParam = options['orderId']
|
||
if (id != null && id != '') {
|
||
orderId.value = id as string
|
||
loadOrderDetail()
|
||
} else if (orderIdParam != null && orderIdParam != '') {
|
||
orderId.value = orderIdParam as string
|
||
loadOrderDetail()
|
||
}
|
||
})
|
||
|
||
</script>
|
||
|
||
<style scoped>
|
||
.order-detail-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.scroll-content {
|
||
flex: 1;
|
||
padding-bottom: 20px;
|
||
}
|
||
|
||
.card {
|
||
background-color: #ffffff;
|
||
margin: 10px;
|
||
padding: 15px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* 状态栏 */
|
||
.order-status {
|
||
background: linear-gradient(135deg, #ff9000, #ff5000);
|
||
padding: 30px 20px;
|
||
color: white;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center; /* 手机端默认居中 */
|
||
}
|
||
|
||
.status-content {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center; /* 确保内容居中 */
|
||
text-align: center; /* 文字居中 */
|
||
}
|
||
|
||
.status-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.status-title-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center; /* 标题行居中 */
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.status-emoji {
|
||
font-size: 28px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.status-desc {
|
||
font-size: 14px;
|
||
opacity: 0.95;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 分享免单入口 */
|
||
.share-free-entry {
|
||
margin-top: 20px;
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
border-radius: 12px;
|
||
padding: 14px 16px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
width: 100%;
|
||
max-width: 400px;
|
||
}
|
||
|
||
.share-free-icon {
|
||
font-size: 28px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.share-free-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.share-free-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: white;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.share-free-desc {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.share-free-arrow {
|
||
font-size: 20px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
/* 配送信息 */
|
||
.section-title {
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.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: 15px;
|
||
padding-top: 15px;
|
||
border-top: 1px solid #f5f5f5;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.courier-icon {
|
||
font-size: 20px;
|
||
margin-right: 10px;
|
||
color: #666;
|
||
}
|
||
|
||
.courier-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.courier-label {
|
||
font-size: 14px;
|
||
color: #333;
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.tracking-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.carrier-name {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.tracking-no {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-right: 10px;
|
||
font-family: monospace; /* 适合单号显示 */
|
||
}
|
||
|
||
.copy-tag {
|
||
background-color: #fff2f0;
|
||
border: 1px solid #ffccc7;
|
||
border-radius: 4px;
|
||
padding: 1px 8px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.copy-tag-text {
|
||
color: #ff4d4f;
|
||
font-size: 11px;
|
||
}
|
||
|
||
/* 店铺与商品 */
|
||
.shop-header {
|
||
display: flex;
|
||
flex-direction: row; /* 显式声明横向 */
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
width: 100%; /* 占满容器 */
|
||
}
|
||
|
||
.shop-icon {
|
||
margin-right: 8px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.shop-name {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
flex: 1; /* 自适应占据空间 */
|
||
color: #333;
|
||
}
|
||
|
||
.arrow-right {
|
||
color: #999;
|
||
font-size: 14px;
|
||
margin-left: auto; /* 确保在最后端 */
|
||
}
|
||
|
||
.product-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-bottom: 15px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.product-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.product-image {
|
||
width: 90px;
|
||
height: 90px;
|
||
border-radius: 8px;
|
||
margin-right: 12px;
|
||
background-color: #f9f9f9;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.product-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
min-height: 90px;
|
||
}
|
||
|
||
.product-name {
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
color: #333;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
lines: 2;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.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;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 5px;
|
||
width: 100%;
|
||
}
|
||
|
||
.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-detail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center; /* 居中显示 */
|
||
padding: 20px 15px;
|
||
}
|
||
|
||
.cost-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
font-size: 14px;
|
||
width: 100%;
|
||
max-width: 400px; /* 控制明细区域宽度,并保持居中感 */
|
||
}
|
||
|
||
.cost-row.total {
|
||
margin-top: 15px;
|
||
padding-top: 15px;
|
||
border-top: 1px solid #f5f5f5;
|
||
align-items: center;
|
||
}
|
||
|
||
.cost-value.price {
|
||
color: #ff5000;
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 底部按钮 */
|
||
.bottom-actions {
|
||
background-color: #ffffff;
|
||
padding: 12px 15px;
|
||
padding-bottom: 30px;
|
||
box-shadow: 0 -2px 15px rgba(0,0,0,0.08);
|
||
}
|
||
|
||
.action-bar-wrapper {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.action-left {
|
||
padding: 0;
|
||
margin-right: 0;
|
||
}
|
||
|
||
.service-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 50px;
|
||
}
|
||
|
||
.service-icon {
|
||
font-size: 20px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.service-label {
|
||
font-size: 11px;
|
||
color: #666;
|
||
}
|
||
|
||
.action-right {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.btn-group {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.btn {
|
||
margin: 0;
|
||
margin-left: 12px;
|
||
padding: 0 18px;
|
||
height: 36px;
|
||
line-height: 36px;
|
||
font-size: 14px;
|
||
border-radius: 18px;
|
||
background: #ffffff;
|
||
border: 1px solid #ddd;
|
||
color: #444;
|
||
}
|
||
|
||
.btn.primary {
|
||
background: linear-gradient(to right, #ff9000, #ff5000);
|
||
color: #ffffff;
|
||
border: none;
|
||
font-weight: bold;
|
||
box-shadow: 0 4px 8px rgba(255, 80, 0, 0.2);
|
||
}
|
||
|
||
.btn.share-free {
|
||
background: linear-gradient(to right, #52c41a, #73d13d);
|
||
color: #ffffff;
|
||
border: none;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 响应式适配 */
|
||
@media screen and (min-width: 768px) {
|
||
.card {
|
||
width: 1200px;
|
||
max-width: 1080px;
|
||
margin: 15px auto;
|
||
padding: 25px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.status-content, .action-bar-wrapper {
|
||
width: 1200px;
|
||
max-width: 1080px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* 优化店铺头部在大屏下的自适应布局 */
|
||
.shop-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 15px 0;
|
||
margin-bottom: 20px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
width: 100%;
|
||
}
|
||
|
||
.shop-icon {
|
||
font-size: 20px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.shop-name {
|
||
font-size: 16px;
|
||
color: #333;
|
||
font-weight: bold;
|
||
flex: 1;
|
||
}
|
||
|
||
.arrow-right {
|
||
font-size: 18px;
|
||
color: #ccc;
|
||
margin-left: auto; /* 确保箭头始终在最右侧 */
|
||
}
|
||
|
||
/* 费用明细在大屏下的居中对齐 */
|
||
.cost-detail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center; /* 整体板块内容在大屏下也保持水平居中 */
|
||
}
|
||
|
||
.cost-row {
|
||
width: 100%;
|
||
max-width: 500px;
|
||
margin-bottom: 12px;
|
||
font-size: 15px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.cost-row.total {
|
||
width: 100%;
|
||
max-width: 500px;
|
||
padding-top: 20px;
|
||
margin-top: 10px;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
|
||
.cost-label {
|
||
font-size: 15px;
|
||
color: #666;
|
||
}
|
||
|
||
.cost-value.price {
|
||
font-size: 28px;
|
||
}
|
||
|
||
/* 配送信息平铺优化 */
|
||
.delivery-info-content {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.delivery-address {
|
||
flex: 2;
|
||
}
|
||
|
||
.courier-info {
|
||
flex: 1;
|
||
border-top: none;
|
||
margin-top: 0;
|
||
padding-top: 0;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* 订单信息在大屏下对齐展示 */
|
||
.order-info {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
}
|
||
.order-info .info-row {
|
||
width: 30%;
|
||
margin: 0 1.5% 10px 1.5%;
|
||
border-bottom: 1px solid #f9f9f9;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 26px;
|
||
}
|
||
|
||
.btn {
|
||
height: 42px;
|
||
line-height: 42px;
|
||
padding: 0 30px;
|
||
font-size: 15px;
|
||
}
|
||
}
|
||
|
||
/* 状态样式 */
|
||
.status-4 .status-text { /* Completed */ }
|
||
</style>
|