1678 lines
42 KiB
Plaintext
1678 lines
42 KiB
Plaintext
<!-- 机构端 - 服务订单详情页面 -->
|
||
<template>
|
||
<view class="detail-page">
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<view class="detail-navbar">
|
||
<view class="detail-navbar-back" @click="uni.navigateBack()">
|
||
<text class="back-arrow">‹</text>
|
||
<text class="back-text">返回</text>
|
||
</view>
|
||
<text class="detail-navbar-title">服务订单详情</text>
|
||
<view style="width: 120rpx;"></view>
|
||
</view>
|
||
<!-- #endif -->
|
||
|
||
<scroll-view class="detail-scroll" direction="vertical">
|
||
|
||
<!-- ① 状态头部 -->
|
||
<view class="status-header" :class="'sh-' + statusKey">
|
||
<view class="sh-main-row">
|
||
<text class="sh-icon">{{ statusIcon }}</text>
|
||
<view class="sh-titles">
|
||
<text class="sh-main-text">{{ statusMainText }}</text>
|
||
<text class="sh-desc-text">{{ statusDescText }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="sh-tags-row">
|
||
<view class="sh-tag" :class="'sht-pay-' + payTagType">
|
||
<text class="sh-tag-label">支付</text>
|
||
<text class="sh-tag-val">{{ payStatusText }}</text>
|
||
</view>
|
||
<view class="sh-tag sht-fulfill">
|
||
<text class="sh-tag-label">服务</text>
|
||
<text class="sh-tag-val">{{ serviceStatusText }}</text>
|
||
</view>
|
||
<view v-if="isAftersale" class="sh-tag sht-aftersale">
|
||
<text class="sh-tag-label">退款</text>
|
||
<text class="sh-tag-val">{{ aftersaleStatusText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ② 上门地址 & 服务安排 -->
|
||
<view class="section-card mt16">
|
||
<view class="section-card-title">
|
||
<text class="sct-icon">📍</text>
|
||
<text class="sct-text">上门地址</text>
|
||
</view>
|
||
<view class="addr-block">
|
||
<view class="addr-person-row">
|
||
<text class="addr-name">{{ addressData.recipient_name || '服务对象未知' }}</text>
|
||
<text class="addr-phone">{{ maskPhone(addressData.phone) }}</text>
|
||
</view>
|
||
<text class="addr-detail">
|
||
{{ addressData.province }}{{ addressData.city }}{{ addressData.district }}{{ addressData.detail_address }}
|
||
</text>
|
||
<text v-if="!addressData.recipient_name && !addressData.detail_address" class="addr-empty">暂无上门地址</text>
|
||
</view>
|
||
<!-- 服务安排信息,已接单后显示 -->
|
||
<view v-if="order.order_status === 3 || order.order_status === 4" class="logistics-divider"></view>
|
||
<view v-if="order.order_status === 3 || order.order_status === 4" class="logistics-block">
|
||
<view class="logi-left">
|
||
<text class="logi-icon">👨⚕️</text>
|
||
<view class="logi-info">
|
||
<text class="logi-company">{{ order.carrier_name || '服务人员待分配' }}</text>
|
||
<view class="logi-no-row">
|
||
<text class="logi-no">工单号:{{ order.tracking_no || '待生成' }}</text>
|
||
<view v-if="order.tracking_no" class="copy-tag" @click="copyText(order.tracking_no)">复制</view>
|
||
</view>
|
||
<text v-if="order.shipped_at" class="logi-time">出发时间:{{ formatTime(order.shipped_at) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ③ 服务/商品项目 -->
|
||
<view class="section-card mt16">
|
||
<view class="section-card-title">
|
||
<text class="sct-icon">🏥</text>
|
||
<text class="sct-text">服务/商品项目</text>
|
||
<text class="sct-count">共 {{ order.items.length }} 项</text>
|
||
</view>
|
||
<view v-if="order.items.length === 0" class="items-empty">
|
||
<text class="items-empty-text">暂无服务项目信息</text>
|
||
</view>
|
||
<view v-for="item in order.items" :key="item.id" class="product-item">
|
||
<image
|
||
:src="item.image_url || '/static/images/default-product.png'"
|
||
class="product-img"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="product-main">
|
||
<text class="product-name">{{ item.product_name }}</text>
|
||
<view v-if="item.sku_name" class="product-spec-wrap">
|
||
<text class="product-spec">{{ item.sku_name }}</text>
|
||
</view>
|
||
<view class="product-price-row">
|
||
<text class="product-price">¥{{ formatMoney(item.price) }}</text>
|
||
<text class="product-qty">× {{ item.quantity }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="product-subtotal">
|
||
<text class="product-subtotal-label">小计</text>
|
||
<text class="product-subtotal-val">¥{{ formatMoney(item.price * item.quantity) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ④ 费用明细 -->
|
||
<view class="section-card mt16">
|
||
<view class="section-card-title">
|
||
<text class="sct-icon">💰</text>
|
||
<text class="sct-text">费用明细</text>
|
||
</view>
|
||
<view class="fee-row">
|
||
<text class="fee-label">服务金额</text>
|
||
<text class="fee-val">¥{{ formatMoney(order.product_amount) }}</text>
|
||
</view>
|
||
<view class="fee-row">
|
||
<text class="fee-label">上门服务费</text>
|
||
<text class="fee-val">¥{{ formatMoney(order.shipping_fee) }}</text>
|
||
</view>
|
||
<view v-if="safeNum(order.discount_amount) > 0" class="fee-row">
|
||
<text class="fee-label">优惠/补贴</text>
|
||
<text class="fee-val fee-red">-¥{{ formatMoney(order.discount_amount) }}</text>
|
||
</view>
|
||
<view class="fee-divider"></view>
|
||
<view class="fee-total-row">
|
||
<text class="fee-total-label">实付金额</text>
|
||
<text class="fee-total-val">¥{{ formatMoney(order.total_amount) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ⑤ 订单信息 -->
|
||
<view class="section-card mt16">
|
||
<view class="section-card-title">
|
||
<text class="sct-icon">📋</text>
|
||
<text class="sct-text">订单信息</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">订单编号</text>
|
||
<view class="info-right">
|
||
<text class="info-val info-code">{{ order.order_no }}</text>
|
||
<view class="copy-tag" @click="copyText(order.order_no)">复制</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="order.payment_method" class="info-row">
|
||
<text class="info-label">支付方式</text>
|
||
<text class="info-val">{{ getPayMethodText(order.payment_method) }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">下单时间</text>
|
||
<text class="info-val">{{ formatTime(order.created_at) }}</text>
|
||
</view>
|
||
<view v-if="order.paid_at" class="info-row">
|
||
<text class="info-label">付款时间</text>
|
||
<text class="info-val">{{ formatTime(order.paid_at) }}</text>
|
||
</view>
|
||
<view v-if="order.shipped_at" class="info-row">
|
||
<text class="info-label">派单时间</text>
|
||
<text class="info-val">{{ formatTime(order.shipped_at) }}</text>
|
||
</view>
|
||
<view v-if="order.completed_at" class="info-row">
|
||
<text class="info-label">完成时间</text>
|
||
<text class="info-val">{{ formatTime(order.completed_at) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ⑥ 备注 & 操作日志 -->
|
||
<view class="section-card mt16">
|
||
<view class="section-card-title">
|
||
<text class="sct-icon">🗒</text>
|
||
<text class="sct-text">备注与日志</text>
|
||
</view>
|
||
<view v-if="order.remark" class="remark-block buyer-remark">
|
||
<view class="remark-header-row">
|
||
<text class="remark-tag buyer">家属备注</text>
|
||
</view>
|
||
<text class="remark-content">{{ order.remark }}</text>
|
||
</view>
|
||
<view v-if="!order.remark" class="remark-block remark-empty-row">
|
||
<text class="remark-tag buyer">家属备注</text>
|
||
<text class="remark-none">无</text>
|
||
</view>
|
||
<view class="remark-block merchant-remark">
|
||
<view class="remark-header-row">
|
||
<text class="remark-tag merchant">机构备注</text>
|
||
<text class="remark-edit-btn" @click="editMerchantRemark">编辑</text>
|
||
</view>
|
||
<text v-if="order.merchant_remark" class="remark-content">{{ order.merchant_remark }}</text>
|
||
<text v-else class="remark-none">暂无机构备注(仅自己可见)</text>
|
||
</view>
|
||
<view class="timeline-wrap">
|
||
<view class="timeline-title-row">
|
||
<text class="timeline-title">操作日志</text>
|
||
</view>
|
||
<view v-if="timeline.length === 0" class="timeline-empty">
|
||
<text class="timeline-empty-text">暂无操作记录</text>
|
||
</view>
|
||
<view v-for="(log, idx) in timeline" :key="idx" class="timeline-item">
|
||
<view class="tl-dot-wrap">
|
||
<view class="tl-dot" :class="idx === 0 ? 'tl-dot-active' : ''"></view>
|
||
<view v-if="idx < timeline.length - 1" class="tl-line"></view>
|
||
</view>
|
||
<view class="tl-content">
|
||
<text class="tl-action">{{ log.action }}</text>
|
||
<text class="tl-time">{{ formatTime(log.created_at) }}</text>
|
||
<text v-if="log.remark" class="tl-remark">{{ log.remark }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ⑦ 退款信息(有售后时显示) -->
|
||
<view v-if="isAftersale" class="section-card mt16 aftersale-card">
|
||
<view class="section-card-title">
|
||
<text class="sct-icon">🔄</text>
|
||
<text class="sct-text">退款信息</text>
|
||
<view class="sct-status-tag">{{ aftersaleStatusText }}</view>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">退款类型</text>
|
||
<text class="info-val">{{ order.refund_type || '退款' }}</text>
|
||
</view>
|
||
<view v-if="safeNum(order.refund_amount) > 0" class="info-row">
|
||
<text class="info-label">退款金额</text>
|
||
<text class="info-val fee-red">¥{{ formatMoney(order.refund_amount) }}</text>
|
||
</view>
|
||
<view v-if="order.refund_reason" class="info-row">
|
||
<text class="info-label">退款原因</text>
|
||
<text class="info-val">{{ order.refund_reason }}</text>
|
||
</view>
|
||
<view class="aftersale-btn-row">
|
||
<view class="aftersale-enter-btn" @click="processAftersale">处理退款 ›</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部安全区占位 -->
|
||
<view style="height: 180rpx;"></view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部固定操作栏 -->
|
||
<view class="action-bar">
|
||
<view class="action-bar-inner">
|
||
<view
|
||
v-for="btn in actionButtons"
|
||
:key="btn.key"
|
||
class="action-btn"
|
||
:class="'ab-' + btn.type"
|
||
@click="handleActionBtn(btn.key)"
|
||
>
|
||
{{ btn.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 安排服务上门弹窗 -->
|
||
<view v-if="showShipModal" class="modal-mask" @click="closeShipModal">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">安排服务上门</text>
|
||
<text class="modal-close" @click="closeShipModal">×</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-item">
|
||
<text class="form-label">服务人员</text>
|
||
<picker
|
||
class="form-picker"
|
||
:range="serviceStaff"
|
||
range-key="name"
|
||
@change="onStaffChange"
|
||
>
|
||
<view class="picker-value">
|
||
{{ selectedStaff.name || '请选择服务人员' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">服务工单号</text>
|
||
<input
|
||
class="form-input"
|
||
v-model="serviceCode"
|
||
placeholder="请输入服务工单号"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<view class="modal-btn cancel" @click="closeShipModal">取消</view>
|
||
<view class="modal-btn confirm" @click="confirmShip">确认派单</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
type OrderItemType = {
|
||
id: string
|
||
order_id: string
|
||
product_id: string
|
||
sku_id: string
|
||
product_name: string
|
||
sku_name: string
|
||
price: number
|
||
quantity: number
|
||
image_url: string
|
||
sku_snapshot: string
|
||
}
|
||
|
||
type AddressType = {
|
||
recipient_name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail_address: string
|
||
}
|
||
|
||
type ServiceStaffType = {
|
||
name: string
|
||
code: string
|
||
}
|
||
|
||
type ActionBtnType = {
|
||
key: string
|
||
label: string
|
||
type: string
|
||
}
|
||
|
||
type TimelineItemType = {
|
||
action: string
|
||
created_at: string
|
||
remark: string
|
||
}
|
||
|
||
// ===== 状态映射常量(医养语义) =====
|
||
const STATUS_KEY_MAP : UTSJSONObject = {
|
||
'1': 'pay', '2': 'pending', '3': 'service', '4': 'done',
|
||
'5': 'cancel', '-1': 'cancel', '0': 'refund', '6': 'refund'
|
||
}
|
||
const STATUS_MAIN_TEXT : UTSJSONObject = {
|
||
'1': '待支付', '2': '待接单', '3': '服务进行中', '4': '服务完成',
|
||
'5': '已取消', '-1': '已取消', '0': '退款处理中', '6': '退款处理中'
|
||
}
|
||
const STATUS_DESC : UTSJSONObject = {
|
||
'1': '家属已下单,等待支付',
|
||
'2': '家属已支付,请尽快安排服务人员上门',
|
||
'3': '服务人员已上门,服务进行中',
|
||
'4': '服务已完成,感谢您的服务',
|
||
'5': '订单已取消',
|
||
'-1': '订单已取消',
|
||
'0': '家属申请退款,请及时处理',
|
||
'6': '家属申请退款,请及时处理'
|
||
}
|
||
const STATUS_ICON : UTSJSONObject = {
|
||
'1': '💳', '2': '📋', '3': '🏥', '4': '✅',
|
||
'5': '❌', '-1': '❌', '0': '🔄', '6': '🔄'
|
||
}
|
||
const PAY_STATUS_TEXT : UTSJSONObject = {
|
||
'1': '待付款', '2': '已付款', '3': '已付款', '4': '已付款',
|
||
'5': '未付款', '-1': '未付款', '0': '退款中', '6': '退款中'
|
||
}
|
||
const SERVICE_STATUS_TEXT : UTSJSONObject = {
|
||
'1': '待支付', '2': '待接单', '3': '服务中', '4': '已完成',
|
||
'5': '已取消', '-1': '已取消', '0': '退款处理', '6': '退款处理'
|
||
}
|
||
const PAY_METHOD_TEXT : UTSJSONObject = {
|
||
'wechat': '微信支付', 'alipay': '支付宝', 'balance': '余额支付',
|
||
'wxpay': '微信支付', 'wx': '微信支付'
|
||
}
|
||
|
||
// 按钮矩阵配置(医养对应语义)
|
||
const ACTION_BUTTONS_MAP : UTSJSONObject = {
|
||
'1': [
|
||
{ key: 'contact', label: '联系家属', type: 'default' },
|
||
{ key: 'close_order', label: '取消订单', type: 'danger' }
|
||
],
|
||
'2': [
|
||
{ key: 'contact', label: '联系家属', type: 'default' },
|
||
{ key: 'ship', label: '安排服务上门', type: 'primary' }
|
||
],
|
||
'3': [
|
||
{ key: 'contact', label: '联系家属', type: 'default' },
|
||
{ key: 'view_progress', label: '服务进度', type: 'default' },
|
||
{ key: 'complete_service', label: '完成服务', type: 'primary' }
|
||
],
|
||
'4': [
|
||
{ key: 'contact', label: '联系家属', type: 'default' },
|
||
{ key: 'service_record', label: '服务记录', type: 'default' }
|
||
],
|
||
'5': [
|
||
{ key: 'close_reason', label: '取消原因', type: 'default' },
|
||
{ key: 'delete', label: '删除订单', type: 'danger' }
|
||
],
|
||
'-1': [
|
||
{ key: 'close_reason', label: '取消原因', type: 'default' },
|
||
{ key: 'delete', label: '删除订单', type: 'danger' }
|
||
],
|
||
'0': [
|
||
{ key: 'contact', label: '联系家属', type: 'default' },
|
||
{ key: 'process_refund', label: '处理退款', type: 'primary' }
|
||
],
|
||
'6': [
|
||
{ key: 'contact', label: '联系家属', type: 'default' },
|
||
{ key: 'process_refund', label: '处理退款', type: 'primary' }
|
||
]
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
orderId: '' as string,
|
||
order: {
|
||
id: '',
|
||
order_no: '',
|
||
user_id: '',
|
||
merchant_id: '',
|
||
order_status: 1,
|
||
total_amount: 0,
|
||
product_amount: 0,
|
||
shipping_fee: 0,
|
||
discount_amount: 0,
|
||
paid_amount: 0,
|
||
refund_amount: 0,
|
||
refund_type: '',
|
||
refund_reason: '',
|
||
shipping_address: '',
|
||
remark: '',
|
||
merchant_remark: '',
|
||
carrier_name: '',
|
||
tracking_no: '',
|
||
payment_method: '',
|
||
paid_at: '',
|
||
shipped_at: '',
|
||
completed_at: '',
|
||
created_at: '',
|
||
updated_at: '',
|
||
items: [] as OrderItemType[]
|
||
},
|
||
addressData: {
|
||
recipient_name: '',
|
||
phone: '',
|
||
province: '',
|
||
city: '',
|
||
district: '',
|
||
detail_address: ''
|
||
} as AddressType,
|
||
timeline: [] as TimelineItemType[],
|
||
|
||
// 派单弹窗
|
||
showShipModal: false as boolean,
|
||
serviceStaff: [
|
||
{ name: '张医师', code: 'ZYS001' },
|
||
{ name: '李护士', code: 'LHS002' },
|
||
{ name: '王康复师', code: 'WKF003' },
|
||
{ name: '陈营养师', code: 'CYY004' },
|
||
{ name: '刘家政员', code: 'LJZ005' }
|
||
] as ServiceStaffType[],
|
||
selectedStaff: { name: '', code: '' } as ServiceStaffType,
|
||
serviceCode: '' as string
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
statusKey(): string {
|
||
const k = String(this.order.order_status)
|
||
return (STATUS_KEY_MAP[k] as string) || 'pay'
|
||
},
|
||
statusMainText(): string {
|
||
const k = String(this.order.order_status)
|
||
return (STATUS_MAIN_TEXT[k] as string) || '未知'
|
||
},
|
||
statusDescText(): string {
|
||
const k = String(this.order.order_status)
|
||
return (STATUS_DESC[k] as string) || ''
|
||
},
|
||
statusIcon(): string {
|
||
const k = String(this.order.order_status)
|
||
return (STATUS_ICON[k] as string) || '📋'
|
||
},
|
||
payStatusText(): string {
|
||
const k = String(this.order.order_status)
|
||
return (PAY_STATUS_TEXT[k] as string) || '-'
|
||
},
|
||
payTagType(): string {
|
||
const s = this.order.order_status
|
||
if (s === 1) return 'pending'
|
||
if (s === 5 || s === -1) return 'none'
|
||
return 'done'
|
||
},
|
||
serviceStatusText(): string {
|
||
const k = String(this.order.order_status)
|
||
return (SERVICE_STATUS_TEXT[k] as string) || '-'
|
||
},
|
||
isAftersale(): boolean {
|
||
return this.order.order_status === 0 || this.order.order_status === 6
|
||
},
|
||
aftersaleStatusText(): string {
|
||
if (this.order.order_status === 0) return '退款中'
|
||
if (this.order.order_status === 6) return '退款处理中'
|
||
return ''
|
||
},
|
||
actionButtons(): ActionBtnType[] {
|
||
const k = String(this.order.order_status)
|
||
const btns = ACTION_BUTTONS_MAP[k]
|
||
if (btns != null && Array.isArray(btns)) {
|
||
return btns as ActionBtnType[]
|
||
}
|
||
return [{ key: 'contact', label: '联系家属', type: 'default' }] as ActionBtnType[]
|
||
}
|
||
},
|
||
|
||
onLoad(options: any) {
|
||
let id = ''
|
||
if (options['id'] != null) {
|
||
id = options['id'] as string
|
||
} else if (options.id != null) {
|
||
id = options.id as string
|
||
}
|
||
if (id !== '') {
|
||
this.orderId = id
|
||
this.loadOrderDetail()
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
|
||
// ===== 工具方法 =====
|
||
|
||
copyText(text: string) {
|
||
if (!text) return
|
||
uni.setClipboardData({
|
||
data: text,
|
||
success: () => { uni.showToast({ title: '复制成功', icon: 'success' }) }
|
||
})
|
||
},
|
||
|
||
formatMoney(amount: any): string {
|
||
const num = Number(amount)
|
||
if (isNaN(num)) return '0.00'
|
||
return num.toFixed(2)
|
||
},
|
||
|
||
safeNum(val: any): number {
|
||
const n = Number(val)
|
||
return isNaN(n) ? 0 : n
|
||
},
|
||
|
||
formatTime(timeStr: string): string {
|
||
if (!timeStr) return '-'
|
||
try {
|
||
const date = new Date(timeStr)
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
const hour = date.getHours().toString().padStart(2, '0')
|
||
const minute = date.getMinutes().toString().padStart(2, '0')
|
||
return `${year}-${month}-${day} ${hour}:${minute}`
|
||
} catch (_e) { return timeStr }
|
||
},
|
||
|
||
maskPhone(phone: string): string {
|
||
if (!phone || phone.length < 7) return phone || ''
|
||
return phone.substring(0, 3) + '****' + phone.substring(phone.length - 4)
|
||
},
|
||
|
||
getPayMethodText(method: string): string {
|
||
if (!method) return '-'
|
||
const t = PAY_METHOD_TEXT[method] as string
|
||
return t || method
|
||
},
|
||
|
||
// ===== 数据加载 =====
|
||
|
||
async loadOrderDetail() {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.select(`*,items:ml_order_items(id,order_id,product_id,sku_id,product_name,sku_name,price,quantity,image_url,specifications)`)
|
||
.eq('id', this.orderId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (response.error != null || (response.status ?? 200) >= 400) {
|
||
console.error('获取订单详情失败:', response.error)
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
let realData = response.data
|
||
if (response.data != null && (response.data as any)['0'] != null) {
|
||
realData = (response.data as any)['0']
|
||
}
|
||
const rawData = realData as UTSJSONObject
|
||
if (rawData == null) return
|
||
|
||
this.order = {
|
||
id: String(rawData['id'] ?? '') || '',
|
||
order_no: String(rawData['order_no'] ?? '') || '',
|
||
user_id: String(rawData['user_id'] ?? '') || '',
|
||
merchant_id: String(rawData['merchant_id'] ?? '') || '',
|
||
order_status: Number(rawData['order_status'] ?? 0) || 1,
|
||
total_amount: Number(rawData['total_amount'] ?? 0) || 0,
|
||
product_amount: Number(rawData['product_amount'] ?? 0) || 0,
|
||
shipping_fee: Number(rawData['shipping_fee'] ?? 0) || 0,
|
||
discount_amount: Number(rawData['discount_amount'] ?? 0) || 0,
|
||
paid_amount: Number(rawData['paid_amount'] ?? 0) || 0,
|
||
refund_amount: Number(rawData['refund_amount'] ?? 0) || 0,
|
||
refund_type: String(rawData['refund_type'] ?? '') || '',
|
||
refund_reason: String(rawData['refund_reason'] ?? '') || '',
|
||
shipping_address: String(rawData['shipping_address'] ?? '') || '{}',
|
||
remark: String(rawData['remark'] ?? '') || '',
|
||
merchant_remark: String(rawData['merchant_remark'] ?? '') || '',
|
||
carrier_name: String(rawData['carrier_name'] ?? '') || '',
|
||
tracking_no: String(rawData['tracking_no'] ?? '') || '',
|
||
payment_method: String(rawData['payment_method'] ?? '') || '',
|
||
paid_at: String(rawData['paid_at'] ?? '') || '',
|
||
shipped_at: String(rawData['shipped_at'] ?? '') || '',
|
||
completed_at: String(rawData['completed_at'] ?? '') || '',
|
||
created_at: String(rawData['created_at'] ?? '') || '',
|
||
updated_at: String(rawData['updated_at'] ?? '') || '',
|
||
items: []
|
||
}
|
||
|
||
// 多策略解析服务项目数组
|
||
let itemsArr : Array<any> | null = rawData.getArray('items')
|
||
if (itemsArr == null || itemsArr.length === 0) {
|
||
itemsArr = rawData.getArray('ml_order_items')
|
||
}
|
||
if (itemsArr == null || itemsArr.length === 0) {
|
||
try {
|
||
const rawStr = JSON.stringify(rawData)
|
||
const parsedObj = JSON.parse(rawStr) as UTSJSONObject
|
||
const fromParsed = parsedObj.getArray('items') ?? parsedObj.getArray('ml_order_items')
|
||
if (fromParsed != null && fromParsed.length > 0) {
|
||
itemsArr = fromParsed
|
||
}
|
||
} catch (_e) { console.warn('[OD] fallback JSON parse 失败', _e) }
|
||
}
|
||
const tempItems : OrderItemType[] = []
|
||
if (itemsArr != null && itemsArr.length > 0) {
|
||
for (let i = 0; i < itemsArr.length; i++) {
|
||
const _rawItem = itemsArr[i]
|
||
const orderItem = (_rawItem instanceof UTSJSONObject ? _rawItem : new UTSJSONObject(_rawItem)) as UTSJSONObject
|
||
tempItems.push({
|
||
id: String(orderItem['id'] ?? '') || '',
|
||
order_id: String(orderItem['order_id'] ?? '') || '',
|
||
product_id: String(orderItem['product_id'] ?? '') || '',
|
||
sku_id: String(orderItem['sku_id'] ?? '') || '',
|
||
product_name: String(orderItem['product_name'] ?? '') || '',
|
||
sku_name: String(orderItem['sku_name'] ?? '') || '',
|
||
price: Number(orderItem['price'] ?? 0) || 0,
|
||
quantity: Number(orderItem['quantity'] ?? 0) || 0,
|
||
image_url: String(orderItem['image_url'] ?? '') || '',
|
||
sku_snapshot: String(orderItem['specifications'] ?? orderItem['sku_name'] ?? '') || ''
|
||
} as OrderItemType)
|
||
}
|
||
}
|
||
this.order.items = tempItems
|
||
|
||
this.parseAddress()
|
||
this.buildTimeline()
|
||
} catch (e) {
|
||
console.error('获取订单详情异常:', e)
|
||
}
|
||
},
|
||
|
||
parseAddress() {
|
||
try {
|
||
const addrStr = this.order.shipping_address
|
||
if (!addrStr || addrStr === '{}') return
|
||
const raw = JSON.parse(addrStr) as UTSJSONObject
|
||
this.addressData = {
|
||
recipient_name: String(raw['recipient_name'] ?? raw['name'] ?? '') || '',
|
||
phone: String(raw['phone'] ?? raw['mobile'] ?? '') || '',
|
||
province: String(raw['province'] ?? '') || '',
|
||
city: String(raw['city'] ?? '') || '',
|
||
district: String(raw['district'] ?? '') || '',
|
||
detail_address: String(raw['detail_address'] ?? raw['address'] ?? raw['detail'] ?? '') || ''
|
||
}
|
||
} catch (e) {
|
||
console.error('解析地址失败:', e)
|
||
}
|
||
},
|
||
|
||
buildTimeline() {
|
||
const tl : TimelineItemType[] = []
|
||
if (this.order.completed_at) {
|
||
tl.push({ action: '服务完成', created_at: this.order.completed_at, remark: '' })
|
||
}
|
||
if (this.order.shipped_at) {
|
||
const rm = this.order.carrier_name ? `服务人员:${this.order.carrier_name}` : ''
|
||
tl.push({ action: '服务人员已出发', created_at: this.order.shipped_at, remark: rm })
|
||
}
|
||
if (this.order.paid_at) {
|
||
tl.push({ action: '家属完成付款', created_at: this.order.paid_at, remark: '' })
|
||
}
|
||
if (this.order.created_at) {
|
||
tl.push({ action: '订单创建', created_at: this.order.created_at, remark: '' })
|
||
}
|
||
this.timeline = tl
|
||
},
|
||
|
||
// ===== 按钮统一分发 =====
|
||
|
||
handleActionBtn(key: string) {
|
||
if (key === 'contact') { this.contactBuyer(); return }
|
||
if (key === 'ship') { this.openShipModal(); return }
|
||
if (key === 'view_progress') { this.viewProgress(); return }
|
||
if (key === 'complete_service') { this.completeService(); return }
|
||
if (key === 'service_record') { uni.showToast({ title: '服务记录开发中', icon: 'none' }); return }
|
||
if (key === 'close_order') { this.cancelOrder(); return }
|
||
if (key === 'delete') { this.deleteOrder(); return }
|
||
if (key === 'process_refund') { this.processAftersale(); return }
|
||
if (key === 'close_reason') {
|
||
uni.showModal({
|
||
title: '取消原因',
|
||
content: this.order.remark || '暂无取消原因记录',
|
||
showCancel: false
|
||
})
|
||
return
|
||
}
|
||
},
|
||
|
||
// ===== 具体操作方法 =====
|
||
|
||
contactBuyer() {
|
||
uni.navigateTo({ url: `/pages/mall/merchant/chat?userId=${this.order.user_id}` })
|
||
},
|
||
|
||
openShipModal() {
|
||
this.showShipModal = true
|
||
},
|
||
|
||
closeShipModal() {
|
||
this.showShipModal = false
|
||
this.selectedStaff = { name: '', code: '' }
|
||
this.serviceCode = ''
|
||
},
|
||
|
||
onStaffChange(e: any) {
|
||
const index = e.detail.value as number
|
||
this.selectedStaff = this.serviceStaff[index]
|
||
},
|
||
|
||
async confirmShip() {
|
||
if (!this.selectedStaff.name) {
|
||
uni.showToast({ title: '请选择服务人员', icon: 'none' }); return
|
||
}
|
||
if (!this.serviceCode) {
|
||
uni.showToast({ title: '请输入服务工单号', icon: 'none' }); return
|
||
}
|
||
try {
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 3,
|
||
shipping_status: 2,
|
||
carrier_name: this.selectedStaff.name,
|
||
tracking_no: this.serviceCode,
|
||
shipped_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', this.order.id)
|
||
.execute()
|
||
|
||
if (response.error != null || (response.status ?? 200) >= 400) {
|
||
const msg = response.error?.message ?? '请检查网络或登录状态'
|
||
uni.showToast({ title: '派单失败:' + msg, icon: 'none', duration: 4000 })
|
||
return
|
||
}
|
||
uni.showToast({ title: '派单成功', icon: 'success' })
|
||
this.closeShipModal()
|
||
this.loadOrderDetail()
|
||
} catch (e) {
|
||
uni.showToast({ title: '派单发生异常', icon: 'none' })
|
||
}
|
||
},
|
||
|
||
viewProgress() {
|
||
uni.navigateTo({ url: `/pages/mall/merchant/logistics?orderId=${this.order.id}` })
|
||
},
|
||
|
||
async completeService() {
|
||
uni.showModal({
|
||
title: '确认完成服务',
|
||
content: '确认服务已完成?完成后将通知家属确认。',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 4,
|
||
completed_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', this.order.id)
|
||
.execute()
|
||
if (response.error != null || (response.status ?? 200) >= 400) {
|
||
uni.showToast({ title: '操作失败', icon: 'none' }); return
|
||
}
|
||
uni.showToast({ title: '服务完成', icon: 'success' })
|
||
this.loadOrderDetail()
|
||
} catch (e) {
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
cancelOrder() {
|
||
uni.showModal({
|
||
title: '取消订单',
|
||
content: '确认取消此服务订单吗?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 5,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', this.order.id)
|
||
.execute()
|
||
if (response.error != null || (response.status ?? 200) >= 400) {
|
||
uni.showToast({ title: '操作失败', icon: 'none' }); return
|
||
}
|
||
uni.showToast({ title: '订单已取消', icon: 'success' })
|
||
this.loadOrderDetail()
|
||
} catch (e) {
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
processAftersale() {
|
||
uni.showToast({ title: '退款处理开发中', icon: 'none' })
|
||
},
|
||
|
||
editMerchantRemark() {
|
||
uni.showModal({
|
||
title: '机构备注',
|
||
content: '该功能需要后端接口支持,待接入',
|
||
showCancel: false
|
||
})
|
||
},
|
||
|
||
async deleteOrder() {
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: '删除后不可恢复,确定要删除吗?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.delete()
|
||
.eq('id', this.order.id)
|
||
.execute()
|
||
if (response.error != null || (response.status ?? 200) >= 400) {
|
||
uni.showToast({ title: '删除失败', icon: 'none' }); return
|
||
}
|
||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||
setTimeout(() => { uni.navigateBack() }, 1500)
|
||
} catch (e) {
|
||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
/* ===== 页面基础 ===== */
|
||
.detail-page {
|
||
background-color: #f0f0f0;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.detail-scroll {
|
||
flex: 1;
|
||
}
|
||
|
||
.mt16 { margin-top: 16rpx; }
|
||
|
||
/* ===== 导航栏 ===== */
|
||
.detail-navbar {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-end;
|
||
background-color: #ffffff;
|
||
border-bottom-width: 1rpx;
|
||
border-bottom-style: solid;
|
||
border-bottom-color: #eeeeee;
|
||
box-sizing: border-box;
|
||
padding-top: var(--status-bar-height);
|
||
height: calc(88rpx + var(--status-bar-height));
|
||
}
|
||
|
||
.detail-navbar-back {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 0 30rpx;
|
||
height: 88rpx;
|
||
width: 120rpx;
|
||
}
|
||
|
||
.back-arrow {
|
||
font-size: 44rpx;
|
||
color: #333333;
|
||
line-height: 1;
|
||
margin-right: 4rpx;
|
||
}
|
||
|
||
.back-text {
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
}
|
||
|
||
.detail-navbar-title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
}
|
||
|
||
/* ===== 状态头部 ===== */
|
||
.status-header {
|
||
padding: 32rpx 30rpx 24rpx 30rpx;
|
||
}
|
||
|
||
/* 医养状态色系 */
|
||
.sh-pay { background: linear-gradient(135deg, #FF7800 0%, #ff9500 100%); }
|
||
.sh-pending { background: linear-gradient(135deg, #A6F1E4 0%, #69DFC2 100%); }
|
||
.sh-service { background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%); }
|
||
.sh-done { background: linear-gradient(135deg, #26A69A 0%, #00796B 100%); }
|
||
.sh-cancel { background: linear-gradient(135deg, #90A4AE 0%, #607D8B 100%); }
|
||
.sh-refund { background: linear-gradient(135deg, #FF7043 0%, #E64A19 100%); }
|
||
|
||
.sh-main-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.sh-icon {
|
||
font-size: 56rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.sh-titles {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sh-main-text {
|
||
font-size: 38rpx;
|
||
font-weight: 700;
|
||
color: #ffffff;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.sh-desc-text {
|
||
font-size: 24rpx;
|
||
color: rgba(255,255,255,0.85);
|
||
}
|
||
|
||
.sh-tags-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.sh-tag {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background-color: rgba(255,255,255,0.18);
|
||
border-radius: 6rpx;
|
||
padding: 6rpx 14rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.sh-tag-label {
|
||
font-size: 20rpx;
|
||
color: rgba(255,255,255,0.75);
|
||
margin-right: 6rpx;
|
||
}
|
||
|
||
.sh-tag-val {
|
||
font-size: 22rpx;
|
||
color: #ffffff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.sht-pay-pending .sh-tag-val { color: #ffe082; }
|
||
.sht-pay-done .sh-tag-val { color: #b9fbc0; }
|
||
.sht-pay-none .sh-tag-val { color: rgba(255,255,255,0.5); }
|
||
.sht-aftersale { background-color: rgba(255,255,255,0.25); }
|
||
|
||
/* ===== 通用卡片 ===== */
|
||
.section-card {
|
||
background-color: #ffffff;
|
||
border-radius: 0;
|
||
}
|
||
|
||
.section-card-title {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 22rpx 30rpx 18rpx 30rpx;
|
||
border-bottom-width: 1rpx;
|
||
border-bottom-style: solid;
|
||
border-bottom-color: #f0f0f0;
|
||
}
|
||
|
||
.sct-icon {
|
||
font-size: 30rpx;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.sct-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
flex: 1;
|
||
}
|
||
|
||
.sct-count {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.sct-status-tag {
|
||
font-size: 22rpx;
|
||
color: #E64A19;
|
||
background-color: #fff3e0;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
/* ===== 上门地址块 ===== */
|
||
.addr-block {
|
||
padding: 20rpx 30rpx;
|
||
}
|
||
|
||
.addr-person-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.addr-name {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.addr-phone {
|
||
font-size: 28rpx;
|
||
color: #555;
|
||
}
|
||
|
||
.addr-detail {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.addr-empty {
|
||
font-size: 26rpx;
|
||
color: #bbb;
|
||
}
|
||
|
||
/* ===== 服务安排块 ===== */
|
||
.logistics-divider {
|
||
height: 1rpx;
|
||
background-color: #f0f0f0;
|
||
margin: 0 30rpx;
|
||
}
|
||
|
||
.logistics-block {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 20rpx 30rpx;
|
||
}
|
||
|
||
.logi-left {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.logi-icon {
|
||
font-size: 36rpx;
|
||
margin-right: 16rpx;
|
||
padding-top: 4rpx;
|
||
}
|
||
|
||
.logi-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.logi-company {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.logi-no-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.logi-no {
|
||
font-size: 24rpx;
|
||
color: #555;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.logi-time {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
}
|
||
|
||
/* ===== 商品/服务列表 ===== */
|
||
.product-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
padding: 20rpx 30rpx;
|
||
border-bottom-width: 1rpx;
|
||
border-bottom-style: solid;
|
||
border-bottom-color: #f5f5f5;
|
||
}
|
||
|
||
.product-img {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border-radius: 8rpx;
|
||
margin-right: 20rpx;
|
||
background-color: #f5f5f5;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.product-main {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
.product-name {
|
||
font-size: 28rpx;
|
||
color: #1a1a1a;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.product-spec-wrap {
|
||
display: inline-flex;
|
||
margin-top: 8rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.product-spec {
|
||
font-size: 22rpx;
|
||
color: #888;
|
||
background-color: #f7f7f7;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 4rpx;
|
||
}
|
||
|
||
.product-price-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.product-price {
|
||
font-size: 26rpx;
|
||
color: #1a1a1a;
|
||
font-weight: 500;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.product-qty {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.product-subtotal {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
margin-left: 16rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.product-subtotal-label {
|
||
font-size: 20rpx;
|
||
color: #bbb;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.product-subtotal-val {
|
||
font-size: 28rpx;
|
||
color: #1a1a1a;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.items-empty {
|
||
padding: 40rpx 30rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
}
|
||
|
||
.items-empty-text {
|
||
font-size: 26rpx;
|
||
color: #bbb;
|
||
}
|
||
|
||
/* ===== 金额明细 ===== */
|
||
.fee-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 14rpx 30rpx;
|
||
}
|
||
|
||
.fee-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.fee-val {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.fee-red { color: #e64a19; }
|
||
|
||
.fee-divider {
|
||
height: 1rpx;
|
||
background-color: #f0f0f0;
|
||
margin: 6rpx 30rpx;
|
||
}
|
||
|
||
.fee-total-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 18rpx 30rpx 22rpx 30rpx;
|
||
}
|
||
|
||
.fee-total-label {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.fee-total-val {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #e64a19;
|
||
}
|
||
|
||
/* ===== 订单信息行 ===== */
|
||
.info-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 16rpx 30rpx;
|
||
border-bottom-width: 1rpx;
|
||
border-bottom-style: solid;
|
||
border-bottom-color: #f5f5f5;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
min-width: 150rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.info-right {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.info-val {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
flex: 1;
|
||
text-align: right;
|
||
}
|
||
|
||
.info-code {
|
||
font-size: 22rpx;
|
||
color: #555;
|
||
}
|
||
|
||
.copy-tag {
|
||
font-size: 20rpx;
|
||
color: #09C39D;
|
||
border-width: 1rpx;
|
||
border-style: solid;
|
||
border-color: #09C39D;
|
||
border-radius: 4rpx;
|
||
padding: 2rpx 10rpx;
|
||
margin-left: 12rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ===== 备注区 ===== */
|
||
.remark-block {
|
||
padding: 16rpx 30rpx;
|
||
border-bottom-width: 1rpx;
|
||
border-bottom-style: solid;
|
||
border-bottom-color: #f5f5f5;
|
||
}
|
||
|
||
.remark-empty-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.remark-header-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.remark-tag {
|
||
font-size: 20rpx;
|
||
border-radius: 4rpx;
|
||
padding: 2rpx 10rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.remark-tag.buyer {
|
||
background-color: #E3F7ED;
|
||
color: #09C39D;
|
||
}
|
||
|
||
.remark-tag.merchant {
|
||
background-color: #fff3e0;
|
||
color: #e64a19;
|
||
}
|
||
|
||
.remark-content {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.remark-none {
|
||
font-size: 24rpx;
|
||
color: #bbb;
|
||
}
|
||
|
||
.remark-edit-btn {
|
||
font-size: 22rpx;
|
||
color: #09C39D;
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* ===== 时间线 ===== */
|
||
.timeline-wrap {
|
||
padding: 16rpx 30rpx 20rpx 30rpx;
|
||
}
|
||
|
||
.timeline-title-row {
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.timeline-title {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.timeline-empty {
|
||
padding: 10rpx 0;
|
||
}
|
||
|
||
.timeline-empty-text {
|
||
font-size: 24rpx;
|
||
color: #ccc;
|
||
}
|
||
|
||
.timeline-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.tl-dot-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
width: 32rpx;
|
||
margin-right: 16rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tl-dot {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-radius: 8rpx;
|
||
background-color: #d0d0d0;
|
||
margin-top: 6rpx;
|
||
}
|
||
|
||
.tl-dot-active {
|
||
background-color: #09C39D;
|
||
}
|
||
|
||
.tl-line {
|
||
width: 2rpx;
|
||
flex: 1;
|
||
min-height: 32rpx;
|
||
background-color: #e8e8e8;
|
||
margin-top: 4rpx;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.tl-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-bottom: 24rpx;
|
||
}
|
||
|
||
.tl-action {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tl-time {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.tl-remark {
|
||
font-size: 22rpx;
|
||
color: #aaa;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
/* ===== 退款卡片 ===== */
|
||
.aftersale-card {
|
||
border-top-width: 4rpx;
|
||
border-top-style: solid;
|
||
border-top-color: #E64A19;
|
||
}
|
||
|
||
.aftersale-btn-row {
|
||
padding: 16rpx 30rpx 20rpx 30rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.aftersale-enter-btn {
|
||
font-size: 26rpx;
|
||
color: #E64A19;
|
||
border-width: 1rpx;
|
||
border-style: solid;
|
||
border-color: #E64A19;
|
||
border-radius: 30rpx;
|
||
padding: 10rpx 30rpx;
|
||
}
|
||
|
||
/* ===== 底部固定操作栏 ===== */
|
||
.action-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 -2rpx 16rpx rgba(0,0,0,0.10);
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
z-index: 100;
|
||
}
|
||
|
||
.action-bar-inner {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
padding: 16rpx 24rpx;
|
||
min-height: 104rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.action-btn {
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
padding: 0 32rpx;
|
||
font-size: 28rpx;
|
||
border-radius: 36rpx;
|
||
margin-left: 16rpx;
|
||
text-align: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.ab-default {
|
||
color: #555;
|
||
border-width: 1rpx;
|
||
border-style: solid;
|
||
border-color: #d0d0d0;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.ab-primary {
|
||
background-color: #09C39D;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.ab-danger {
|
||
background-color: #ffffff;
|
||
color: #e64a19;
|
||
border-width: 1rpx;
|
||
border-style: solid;
|
||
border-color: #e64a19;
|
||
}
|
||
|
||
/* ===== 弹窗 ===== */
|
||
.modal-mask {
|
||
position: fixed;
|
||
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: flex-end;
|
||
z-index: 200;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 100%;
|
||
background-color: #fff;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
border-bottom-width: 1rpx;
|
||
border-bottom-style: solid;
|
||
border-bottom-color: #f5f5f5;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.modal-close {
|
||
font-size: 44rpx;
|
||
color: #999;
|
||
line-height: 1;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
display: block;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
|
||
.form-picker {
|
||
height: 72rpx;
|
||
border-width: 1rpx;
|
||
border-style: solid;
|
||
border-color: #e5e5e5;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.form-input {
|
||
height: 72rpx;
|
||
border-width: 1rpx;
|
||
border-style: solid;
|
||
border-color: #e5e5e5;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.picker-value {
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
color: #333;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
flex-direction: row;
|
||
border-top-width: 1rpx;
|
||
border-top-style: solid;
|
||
border-top-color: #f5f5f5;
|
||
}
|
||
|
||
.modal-btn {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.modal-btn.cancel {
|
||
color: #666;
|
||
border-right-width: 1rpx;
|
||
border-right-style: solid;
|
||
border-right-color: #f5f5f5;
|
||
}
|
||
|
||
.modal-btn.confirm {
|
||
color: #09C39D;
|
||
font-weight: bold;
|
||
}
|
||
</style> |