768 lines
22 KiB
Vue
768 lines
22 KiB
Vue
<template>
|
|
<main class="after-sale-detail-page">
|
|
<van-nav-bar title="售后详情" left-arrow @click-left="$router.replace('/joyful-selection/merchant')" />
|
|
|
|
<section v-if="loading" class="skeleton-card">
|
|
<van-skeleton title :row="3" />
|
|
<van-skeleton title :row="5" />
|
|
<van-skeleton title :row="3" />
|
|
</section>
|
|
|
|
<section v-show="!loading" class="status-card" :class="statusCardClass">
|
|
<div class="status-icon">
|
|
<van-icon :name="statusIcon" />
|
|
</div>
|
|
<div>
|
|
<h2>{{ statusTitle }}</h2>
|
|
<p>{{ statusDesc }}</p>
|
|
<button v-if="afterSale.auditRemark" type="button" @click="showAuditRemark">查看审核意见</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-show="!loading" class="detail-card progress-card">
|
|
<h3>售后进度</h3>
|
|
<div class="timeline">
|
|
<div v-for="step in progressSteps" :key="step.key" class="timeline-item" :class="step.state">
|
|
<span class="timeline-dot">
|
|
<van-icon v-if="step.state === 'done'" name="success" />
|
|
</span>
|
|
<div class="timeline-main">
|
|
<b>{{ step.title }}</b>
|
|
<em>{{ step.time || step.subTitle }}</em>
|
|
</div>
|
|
<p>{{ step.desc }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section v-show="!loading" class="detail-card product-card">
|
|
<h3>售后商品</h3>
|
|
<article v-for="item in products" :key="item.id" class="product-row">
|
|
<van-image class="product-thumb" :src="item.image" fit="cover" />
|
|
<div class="product-info">
|
|
<h4>{{ item.name || '-' }}</h4>
|
|
<p>{{ skuText(item) }}</p>
|
|
<b>¥{{ item.price || afterSale.amount || '0.00' }}</b>
|
|
</div>
|
|
<span>x{{ item.number || 1 }}</span>
|
|
</article>
|
|
</section>
|
|
|
|
<section v-if="!loading && showReturnInfo" class="detail-card address-card">
|
|
<h3>{{ returnInfoTitle }}</h3>
|
|
<p><span>联系人</span><b>{{ afterSale.pickupName || '-' }}</b></p>
|
|
<p><span>联系电话</span><b>{{ maskedMobile(afterSale.pickupMobile) }}</b></p>
|
|
<p><span>地址</span><b>{{ afterSale.pickupAddress || '-' }}</b></p>
|
|
<p v-if="afterSale.userLogisticsNo" class="logistics-row">
|
|
<span>寄回单号</span><b>{{ afterSale.userLogisticsCompany || '快递' }} {{ afterSale.userLogisticsNo }}</b>
|
|
</p>
|
|
<p v-if="afterSale.merchantLogisticsNo" class="logistics-row">
|
|
<span>换货单号</span><b>{{ afterSale.merchantLogisticsCompany || '快递' }} {{ afterSale.merchantLogisticsNo }}</b>
|
|
</p>
|
|
</section>
|
|
|
|
<section v-show="!loading" class="detail-card info-card">
|
|
<h3>售后信息</h3>
|
|
<p class="service-no-row"><span>售后单号</span><b class="scrollable-no">{{ afterSale.serviceOrderId || '-' }}</b><button type="button" @click="copyServiceNo">复制</button></p>
|
|
<p><span>子订单号</span><b>{{ afterSale.orderNumberId || afterSale.subOrderId || '-' }}</b></p>
|
|
<p><span>售后类型</span><b>{{ afterSale.serviceTypeText || '-' }}</b></p>
|
|
<p><span>售后原因</span><b>{{ afterSale.reason || '-' }}</b></p>
|
|
<p><span>问题描述</span><b>{{ afterSale.problemDescription || '-' }}</b></p>
|
|
<p><span>申请时间</span><b>{{ afterSale.applyTime || '-' }}</b></p>
|
|
<p v-if="!isUnsignedAfterSale"><span>取件方式</span><b>{{ pickupMethodText }}</b></p>
|
|
<p v-if="!isUnsignedAfterSale"><span>取件地址</span><b>{{ pickupAddressText }}</b></p>
|
|
<div v-if="images.length" class="voucher-block">
|
|
<h4>上传凭证</h4>
|
|
<div>
|
|
<van-image v-for="image in images" :key="image.url" :src="image.url" fit="cover" />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<footer v-show="!loading && merchantActions.length" class="bottom-actions">
|
|
<button
|
|
v-for="action in merchantActions"
|
|
:key="action.key"
|
|
type="button"
|
|
:class="{ primary: action.primary }"
|
|
@click="handleAction(action.key)"
|
|
>
|
|
{{ action.text }}
|
|
</button>
|
|
</footer>
|
|
|
|
<van-popup v-model="showExchangeShipPopup" round position="bottom">
|
|
<section class="action-popup">
|
|
<h3>填写换货发货信息</h3>
|
|
<van-field v-model="exchangeShipForm.logisticsCompany" label="快递公司" placeholder="请输入快递公司" />
|
|
<van-field v-model="exchangeShipForm.logisticsNo" label="快递单号" placeholder="请输入快递单号" />
|
|
<button type="button" @click="submitExchangeShip">确认发货</button>
|
|
</section>
|
|
</van-popup>
|
|
</main>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapActions, mapState } from 'pinia'
|
|
import { Dialog, Toast } from 'vant'
|
|
import { postFunction } from '@/api/supplied-service'
|
|
import { useUserStore } from '@/store/user'
|
|
|
|
const SERVICE_TYPE = {
|
|
RETURN: 4,
|
|
EXCHANGE: 5
|
|
}
|
|
|
|
const SERVICE_STATE = {
|
|
PENDING_AUDIT: 0,
|
|
APPROVED: 1,
|
|
REJECTED: 2,
|
|
USER_SHIPPED: 3,
|
|
RECEIVED: 4,
|
|
FINISHED: 5,
|
|
CANCELLED: 6
|
|
}
|
|
|
|
const LOGISTICS_STATE = {
|
|
NONE: 0,
|
|
WAIT_USER_SHIP: 1,
|
|
USER_SHIPPED: 2,
|
|
MERCHANT_RECEIVED: 3,
|
|
MERCHANT_SHIPPED: 4,
|
|
USER_RECEIVED: 5
|
|
}
|
|
|
|
const SKU_COLOR_MAP = {
|
|
cream: '奶油白',
|
|
blue: '雾霾蓝',
|
|
orange: '暖橙色'
|
|
}
|
|
|
|
const SKU_SIZE_MAP = {
|
|
standard: '标准款',
|
|
plus: '升级款'
|
|
}
|
|
|
|
export default {
|
|
name: 'JoyfulSelectionMerchantAfterSaleDetail',
|
|
data: () => ({
|
|
afterSale: {},
|
|
loading: false,
|
|
showExchangeShipPopup: false,
|
|
exchangeShipForm: {
|
|
logisticsCompany: '',
|
|
logisticsNo: ''
|
|
}
|
|
}),
|
|
computed: {
|
|
...mapState(useUserStore, ['supplierId']),
|
|
products() {
|
|
return this.afterSale.products || []
|
|
},
|
|
images() {
|
|
return Array.isArray(this.afterSale.images)
|
|
? this.afterSale.images.map(item => ({
|
|
url: item.url || item.previewUrl || item
|
|
})).filter(item => item.url)
|
|
: []
|
|
},
|
|
isExchange() {
|
|
return Number(this.afterSale.serviceType) === SERVICE_TYPE.EXCHANGE
|
|
},
|
|
isUnsignedAfterSale() {
|
|
if (this.afterSale.isSigned === false || this.afterSale.isSigned === 0 || this.afterSale.isSigned === 'false') {
|
|
return true
|
|
}
|
|
|
|
return this.afterSale.isSigned === undefined &&
|
|
Number(this.afterSale.logisticsState) === LOGISTICS_STATE.NONE &&
|
|
!this.afterSale.pickupMethod &&
|
|
!this.afterSale.pickupName &&
|
|
!this.afterSale.pickupMobile &&
|
|
!this.afterSale.pickupAddress
|
|
},
|
|
statusCardClass() {
|
|
if (Number(this.afterSale.serviceState) === SERVICE_STATE.REJECTED) return 'rejected'
|
|
if (Number(this.afterSale.serviceState) === SERVICE_STATE.APPROVED) return 'approved'
|
|
if (Number(this.afterSale.serviceState) === SERVICE_STATE.FINISHED) return 'finished'
|
|
return 'pending'
|
|
},
|
|
statusIcon() {
|
|
if (this.statusCardClass === 'approved' || this.statusCardClass === 'finished') return 'success'
|
|
if (this.statusCardClass === 'rejected') return 'cross'
|
|
return 'clock-o'
|
|
},
|
|
statusTitle() {
|
|
const serviceState = Number(this.afterSale.serviceState)
|
|
const logisticsState = Number(this.afterSale.logisticsState)
|
|
if (serviceState === SERVICE_STATE.REJECTED) return '审核未通过'
|
|
if (serviceState === SERVICE_STATE.FINISHED) return '售后完成'
|
|
if (logisticsState === LOGISTICS_STATE.MERCHANT_SHIPPED) return '商家已发货'
|
|
if (logisticsState === LOGISTICS_STATE.MERCHANT_RECEIVED && this.isExchange) return '待商家发货'
|
|
if (logisticsState === LOGISTICS_STATE.USER_SHIPPED) return '用户已寄回'
|
|
if (serviceState === SERVICE_STATE.APPROVED) return '审核通过'
|
|
return '待审核'
|
|
},
|
|
statusDesc() {
|
|
const serviceState = Number(this.afterSale.serviceState)
|
|
const logisticsState = Number(this.afterSale.logisticsState)
|
|
if (serviceState === SERVICE_STATE.REJECTED) return '商家已拒绝该售后申请'
|
|
if (serviceState === SERVICE_STATE.FINISHED) return '售后流程已完成'
|
|
if (logisticsState === LOGISTICS_STATE.MERCHANT_SHIPPED) return '换货商品已发出,等待用户收货'
|
|
if (logisticsState === LOGISTICS_STATE.MERCHANT_RECEIVED && this.isExchange) return '已收到寄回商品,请发出换货商品'
|
|
if (logisticsState === LOGISTICS_STATE.USER_SHIPPED) return '用户已寄回商品,请确认收货'
|
|
if (this.isExchange && this.isUnsignedAfterSale && serviceState === SERVICE_STATE.APPROVED) return '未签收换货申请已通过,请发出换货商品'
|
|
if (serviceState === SERVICE_STATE.APPROVED) return '已通过售后申请,等待用户寄回商品'
|
|
return '请审核用户提交的售后申请'
|
|
},
|
|
progressSteps() {
|
|
const serviceState = Number(this.afterSale.serviceState)
|
|
const logisticsState = Number(this.afterSale.logisticsState)
|
|
const steps = [
|
|
{
|
|
key: 'apply',
|
|
title: '提交申请',
|
|
time: this.afterSale.applyTime,
|
|
desc: '用户已提交售后申请',
|
|
state: 'done'
|
|
},
|
|
{
|
|
key: 'audit',
|
|
title: '商家审核',
|
|
time: this.afterSale.auditTime,
|
|
desc: serviceState === SERVICE_STATE.REJECTED ? '商家审核未通过' : serviceState >= SERVICE_STATE.APPROVED ? '商家已审核通过' : '待商家审核',
|
|
state: serviceState >= SERVICE_STATE.APPROVED || serviceState === SERVICE_STATE.REJECTED ? 'done' : 'current'
|
|
},
|
|
{
|
|
key: 'userShip',
|
|
title: '寄回商品',
|
|
time: this.afterSale.userShipTime,
|
|
desc: logisticsState >= LOGISTICS_STATE.USER_SHIPPED ? '用户已寄回商品' : '等待用户寄回商品',
|
|
state: logisticsState >= LOGISTICS_STATE.USER_SHIPPED ? 'done' : logisticsState === LOGISTICS_STATE.WAIT_USER_SHIP ? 'current' : 'todo'
|
|
},
|
|
{
|
|
key: 'merchantReceive',
|
|
title: '商家收货',
|
|
time: this.afterSale.merchantReceiveTime,
|
|
desc: logisticsState >= LOGISTICS_STATE.MERCHANT_RECEIVED ? '商家已收到商品' : '商家尚未收到商品',
|
|
state: logisticsState >= LOGISTICS_STATE.MERCHANT_RECEIVED ? 'done' : logisticsState === LOGISTICS_STATE.USER_SHIPPED ? 'current' : 'todo'
|
|
}
|
|
]
|
|
|
|
if (this.isUnsignedAfterSale && !this.isExchange) {
|
|
steps.splice(2)
|
|
steps.push({
|
|
key: 'finish',
|
|
title: '售后完成',
|
|
time: this.afterSale.finishTime,
|
|
desc: serviceState === SERVICE_STATE.FINISHED ? '售后已完成' : '审核通过后售后完成',
|
|
state: serviceState === SERVICE_STATE.FINISHED ? 'done' : serviceState >= SERVICE_STATE.APPROVED ? 'current' : 'todo'
|
|
})
|
|
|
|
return this.normalizeSteps(steps)
|
|
}
|
|
|
|
if (this.isExchange) {
|
|
if (this.isUnsignedAfterSale) {
|
|
steps.splice(2)
|
|
}
|
|
|
|
steps.push(
|
|
{
|
|
key: 'merchantShip',
|
|
title: '商家发货',
|
|
time: this.afterSale.merchantShipTime,
|
|
desc: logisticsState >= LOGISTICS_STATE.MERCHANT_SHIPPED ? '商家已发出换货商品' : '等待商家发出换货商品',
|
|
state: logisticsState >= LOGISTICS_STATE.MERCHANT_SHIPPED ? 'done' : (this.isUnsignedAfterSale ? serviceState >= SERVICE_STATE.APPROVED : logisticsState === LOGISTICS_STATE.MERCHANT_RECEIVED) ? 'current' : 'todo'
|
|
},
|
|
{
|
|
key: 'userReceive',
|
|
title: '用户收货',
|
|
time: this.afterSale.finishTime,
|
|
desc: logisticsState >= LOGISTICS_STATE.USER_RECEIVED ? '用户已收到换货商品' : '等待用户收货',
|
|
state: logisticsState >= LOGISTICS_STATE.USER_RECEIVED ? 'done' : logisticsState === LOGISTICS_STATE.MERCHANT_SHIPPED ? 'current' : 'todo'
|
|
}
|
|
)
|
|
}
|
|
|
|
steps.push({
|
|
key: 'finish',
|
|
title: '售后完成',
|
|
time: this.afterSale.finishTime,
|
|
desc: serviceState === SERVICE_STATE.FINISHED ? '售后流程已完成' : '售后流程未完成',
|
|
state: serviceState === SERVICE_STATE.FINISHED ? 'done' : !this.isExchange && logisticsState === LOGISTICS_STATE.MERCHANT_RECEIVED ? 'current' : 'todo'
|
|
})
|
|
|
|
return this.normalizeSteps(steps)
|
|
},
|
|
showReturnInfo() {
|
|
return Number(this.afterSale.serviceState) >= SERVICE_STATE.APPROVED && !this.isUnsignedAfterSale
|
|
},
|
|
returnInfoTitle() {
|
|
return Number(this.afterSale.logisticsState) >= LOGISTICS_STATE.WAIT_USER_SHIP ? '退货地址' : '取件地址'
|
|
},
|
|
pickupMethodText() {
|
|
return this.afterSale.pickupMethod === 'mail' ? '自行寄回' : '上门取件'
|
|
},
|
|
pickupAddressText() {
|
|
return this.afterSale.pickupAddress || '-'
|
|
},
|
|
merchantActions() {
|
|
const serviceState = Number(this.afterSale.serviceState)
|
|
if (serviceState === SERVICE_STATE.PENDING_AUDIT) {
|
|
return [
|
|
{ key: 'reject', text: '拒绝' },
|
|
{ key: 'approve', text: '通过', primary: true }
|
|
]
|
|
}
|
|
|
|
if (Number(this.afterSale.serviceState) === SERVICE_STATE.USER_SHIPPED) {
|
|
return [{ key: 'receive', text: '确认收货', primary: true }]
|
|
}
|
|
|
|
if (this.canShipExchange) {
|
|
return [{ key: 'shipExchange', text: '换货发货', primary: true }]
|
|
}
|
|
|
|
return []
|
|
},
|
|
canShipExchange() {
|
|
return this.isExchange &&
|
|
(Number(this.afterSale.serviceState) === SERVICE_STATE.RECEIVED ||
|
|
(this.afterSale.isSigned === false && Number(this.afterSale.serviceState) === SERVICE_STATE.APPROVED))
|
|
}
|
|
},
|
|
created() {
|
|
this.loadDetail()
|
|
},
|
|
methods: {
|
|
...mapActions(useUserStore, ['fetchBridgeToken']),
|
|
normalizeSteps(steps) {
|
|
const serviceState = Number(this.afterSale.serviceState)
|
|
return steps.map((step, index) => ({
|
|
...step,
|
|
state: serviceState === SERVICE_STATE.REJECTED && index > 1 ? 'todo' : step.state,
|
|
subTitle: step.state === 'done' ? '' : '-'
|
|
}))
|
|
},
|
|
skuText(item = {}) {
|
|
const sku = String(item.sku || item.skuText || '').trim()
|
|
const parts = sku.split('-').slice(-2).filter(Boolean)
|
|
const color = SKU_COLOR_MAP[parts[0]] || parts[0]
|
|
const size = SKU_SIZE_MAP[parts[1]] || parts[1]
|
|
|
|
return color || size ? [color, size].filter(Boolean).join(' | ') : '-'
|
|
},
|
|
maskedMobile(mobile = '') {
|
|
return String(mobile).replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2') || '-'
|
|
},
|
|
async loadDetail() {
|
|
try {
|
|
this.loading = true
|
|
await this.fetchBridgeToken()
|
|
const data = await postFunction('merchant-after-sale-detail', {
|
|
supplierId: this.supplierId,
|
|
serviceOrderId: String(this.$route.query.serviceOrderId || '')
|
|
})
|
|
|
|
this.afterSale = data?.afterSale || {}
|
|
} catch (error) {
|
|
Toast(error.message || '查询售后详情失败,请稍后再试')
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
copyServiceNo() {
|
|
if (navigator.clipboard && this.afterSale.serviceOrderId) {
|
|
navigator.clipboard.writeText(this.afterSale.serviceOrderId)
|
|
}
|
|
Toast('已复制')
|
|
},
|
|
showAuditRemark() {
|
|
Dialog.alert({
|
|
title: '审核意见',
|
|
message: this.afterSale.auditRemark
|
|
})
|
|
},
|
|
handleAction(key) {
|
|
if (key === 'approve') {
|
|
this.auditAfterSale(1)
|
|
return
|
|
}
|
|
if (key === 'reject') {
|
|
this.auditAfterSale(2)
|
|
return
|
|
}
|
|
if (key === 'receive') {
|
|
this.receiveAfterSale()
|
|
return
|
|
}
|
|
if (key === 'shipExchange') {
|
|
this.openExchangeShipPopup()
|
|
}
|
|
},
|
|
async auditAfterSale(auditStatus) {
|
|
await Dialog.confirm({
|
|
title: auditStatus === 1 ? '确认通过售后申请?' : '确认拒绝售后申请?',
|
|
message: auditStatus === 1 ? '通过后用户可寄回商品。' : '拒绝后该售后申请将结束。'
|
|
})
|
|
await postFunction('merchant-after-sale-audit', {
|
|
supplierId: this.supplierId,
|
|
serviceOrderId: this.afterSale.serviceOrderId,
|
|
auditStatus,
|
|
auditRemark: auditStatus === 1 ? '审核通过' : '商家拒绝'
|
|
})
|
|
Toast('处理成功')
|
|
await this.loadDetail()
|
|
},
|
|
async receiveAfterSale() {
|
|
await Dialog.confirm({
|
|
title: '确认已收到寄回商品?',
|
|
message: '确认后退货售后会进入完成,换货售后会进入待商家寄货。'
|
|
})
|
|
await postFunction('merchant-after-sale-receive', {
|
|
supplierId: this.supplierId,
|
|
serviceOrderId: this.afterSale.serviceOrderId
|
|
})
|
|
Toast('已确认收货')
|
|
await this.loadDetail()
|
|
},
|
|
openExchangeShipPopup() {
|
|
this.exchangeShipForm = {
|
|
logisticsCompany: '',
|
|
logisticsNo: ''
|
|
}
|
|
this.showExchangeShipPopup = true
|
|
},
|
|
async submitExchangeShip() {
|
|
if (!this.exchangeShipForm.logisticsCompany || !this.exchangeShipForm.logisticsNo) {
|
|
Toast('请填写完整发货信息')
|
|
return
|
|
}
|
|
|
|
await postFunction('merchant-after-sale-ship', {
|
|
supplierId: this.supplierId,
|
|
serviceOrderId: this.afterSale.serviceOrderId,
|
|
logisticsCompany: this.exchangeShipForm.logisticsCompany,
|
|
logisticsNo: this.exchangeShipForm.logisticsNo
|
|
})
|
|
Toast('换货发货成功')
|
|
this.showExchangeShipPopup = false
|
|
await this.loadDetail()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.after-sale-detail-page {
|
|
min-height: 100vh;
|
|
padding: 56px 10px 78px;
|
|
background: #f7f8fa;
|
|
}
|
|
|
|
.after-sale-detail-page ::v-deep .van-nav-bar {
|
|
position: fixed;
|
|
z-index: 20;
|
|
top: 0;
|
|
right: 0;
|
|
left: 0;
|
|
}
|
|
|
|
.status-card,
|
|
.skeleton-card,
|
|
.detail-card {
|
|
margin-bottom: 10px;
|
|
border-radius: 8px;
|
|
background: #fff;
|
|
}
|
|
|
|
.skeleton-card {
|
|
padding: 18px 14px;
|
|
}
|
|
|
|
.status-card {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding: 18px 14px;
|
|
}
|
|
|
|
.status-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: none;
|
|
width: 42px;
|
|
height: 42px;
|
|
border-radius: 50%;
|
|
color: #fff;
|
|
font-size: 24px;
|
|
background: #ff9f1a;
|
|
}
|
|
|
|
.status-card.approved .status-icon,
|
|
.status-card.finished .status-icon {
|
|
background: #20b879;
|
|
}
|
|
|
|
.status-card.rejected .status-icon {
|
|
background: #ee3f4d;
|
|
}
|
|
|
|
.status-card h2 {
|
|
margin: 0 0 6px;
|
|
color: #222;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.status-card p {
|
|
margin: 0;
|
|
color: #666;
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.status-card button {
|
|
margin-top: 8px;
|
|
border: 0;
|
|
color: #ff5b20;
|
|
font-size: 12px;
|
|
background: transparent;
|
|
}
|
|
|
|
.info-card button {
|
|
flex: none;
|
|
align-self: center;
|
|
height: 24px;
|
|
margin-left: 8px;
|
|
padding: 0;
|
|
border: 0;
|
|
color: #ff5b20;
|
|
font-size: 12px;
|
|
background: transparent;
|
|
}
|
|
|
|
.detail-card {
|
|
padding: 14px 12px;
|
|
}
|
|
|
|
.detail-card h3 {
|
|
margin: 0 0 12px;
|
|
color: #222;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-left: 24px;
|
|
padding-bottom: 16px;
|
|
}
|
|
|
|
.timeline-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 12px;
|
|
bottom: -2px;
|
|
left: 6px;
|
|
width: 1px;
|
|
background: #e5e5e5;
|
|
}
|
|
|
|
.timeline-item:last-child {
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.timeline-item:last-child::before {
|
|
display: none;
|
|
}
|
|
|
|
.timeline-dot {
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 13px;
|
|
height: 13px;
|
|
border-radius: 50%;
|
|
color: #fff;
|
|
font-size: 10px;
|
|
background: #d8d8d8;
|
|
}
|
|
|
|
.timeline-item.done .timeline-dot {
|
|
background: #20b879;
|
|
}
|
|
|
|
.timeline-item.current .timeline-dot {
|
|
background: #ff5b20;
|
|
}
|
|
|
|
.timeline-main {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
}
|
|
|
|
.timeline-main b {
|
|
color: #222;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.timeline-main em,
|
|
.timeline-item p {
|
|
color: #999;
|
|
font-size: 12px;
|
|
font-style: normal;
|
|
}
|
|
|
|
.timeline-item p {
|
|
margin: 6px 0 0;
|
|
}
|
|
|
|
.product-row {
|
|
display: flex;
|
|
gap: 10px;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.product-thumb {
|
|
flex: none;
|
|
width: 72px;
|
|
height: 72px;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.product-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.product-info h4 {
|
|
margin: 0 0 6px;
|
|
color: #222;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.product-info p {
|
|
margin: 0 0 8px;
|
|
color: #999;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.product-info b {
|
|
color: #ff5b20;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.product-row > span {
|
|
color: #999;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.address-card p,
|
|
.info-card p {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin: 10px 0 0;
|
|
color: #999;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.address-card b,
|
|
.info-card b {
|
|
flex: 1;
|
|
color: #333;
|
|
font-weight: 500;
|
|
text-align: right;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.info-card .service-no-row {
|
|
align-items: center;
|
|
}
|
|
|
|
.info-card .scrollable-no {
|
|
min-width: 0;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
white-space: nowrap;
|
|
word-break: normal;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.info-card .scrollable-no::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.voucher-block h4 {
|
|
margin: 14px 0 8px;
|
|
color: #333;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.voucher-block div {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.voucher-block .van-image {
|
|
width: 72px;
|
|
height: 72px;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bottom-actions {
|
|
position: fixed;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
z-index: 10;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
|
|
background: #fff;
|
|
box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
.bottom-actions button,
|
|
.action-popup button {
|
|
min-width: 96px;
|
|
height: 36px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 18px;
|
|
color: #333;
|
|
font-size: 13px;
|
|
background: #fff;
|
|
}
|
|
|
|
.bottom-actions button.primary,
|
|
.action-popup button {
|
|
border-color: #ff5b20;
|
|
color: #fff;
|
|
background: #ff5b20;
|
|
}
|
|
|
|
.action-popup {
|
|
padding: 18px 14px 22px;
|
|
}
|
|
|
|
.action-popup h3 {
|
|
margin: 0 0 12px;
|
|
color: #222;
|
|
font-size: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
.action-popup button {
|
|
width: 100%;
|
|
margin-top: 12px;
|
|
}
|
|
</style>
|