Files
medical-mall/pages/mall/consumer/order-detail.uvue

1345 lines
37 KiB
Plaintext
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="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>