|
@@ -0,0 +1,510 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="order-refund-page">
|
|
|
|
|
+ <div class="page-header">
|
|
|
|
|
+ <NButton text @click="handleBack">
|
|
|
|
|
+ ← 返回列表
|
|
|
|
|
+ </NButton>
|
|
|
|
|
+ <span class="page-title">订单详情 · {{ orderData?.orderNo || '' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="page-subtitle">
|
|
|
|
|
+ 宾馆前台可查看完整信息,并可在此页面进行退单操作
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <NSpin :show="loading">
|
|
|
|
|
+ <div v-if="orderData" class="refund-content">
|
|
|
|
|
+ <!-- 订单信息卡片 -->
|
|
|
|
|
+ <div class="info-card">
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <span class="room-name">{{ orderData.roomNo }} {{ orderData.roomName || '' }}</span>
|
|
|
|
|
+ <div class="order-meta">
|
|
|
|
|
+ {{ orderData.orderNo }} · {{ orderData.createTime }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <DictTag dict-type="hotel_order_status" :value="String(orderData.status)" size="medium" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="card-info-row">
|
|
|
|
|
+ <div class="info-item">
|
|
|
|
|
+ <span class="info-label">联系人</span>
|
|
|
|
|
+ <span class="info-value">{{ orderData.contactName }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="info-item">
|
|
|
|
|
+ <span class="info-label">联系电话</span>
|
|
|
|
|
+ <span class="info-value">{{ orderData.contactPhone }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="info-item">
|
|
|
|
|
+ <span class="info-label">支付方式</span>
|
|
|
|
|
+ <span class="info-value">
|
|
|
|
|
+ <DictTag v-if="orderData.payMethod" dict-type="hotel_pay_method" :value="orderData.payMethod" size="small" />
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="info-item">
|
|
|
|
|
+ <span class="info-label">金额</span>
|
|
|
|
|
+ <span class="info-value amount">¥{{ orderData.totalAmount }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 菜品明细 -->
|
|
|
|
|
+ <div class="items-section">
|
|
|
|
|
+ <div class="items-title">
|
|
|
|
|
+ 菜品明细
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(item, idx) in (orderData.items || [])"
|
|
|
|
|
+ :key="idx"
|
|
|
|
|
+ class="item-row"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span>{{ item.dishName }} ×{{ item.quantity }} — ¥{{ item.subtotal }}</span>
|
|
|
|
|
+ <span v-if="item.specDesc" class="item-spec">({{ item.specDesc }})</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="items-total">
|
|
|
|
|
+ 合计 ¥{{ orderData.totalAmount }} · 免配送费
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 退单操作区域 -->
|
|
|
|
|
+ <div class="refund-action-card">
|
|
|
|
|
+ <div class="refund-warning-title">
|
|
|
|
|
+ ⚠ 退单操作
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="refund-warning-text">
|
|
|
|
|
+ 该订单当前状态为「<DictTag dict-type="hotel_order_status" :value="String(orderData.status)" size="small" />」,
|
|
|
|
|
+ 宾馆前台可在任何阶段发起退单。退单后款项将原路返回至客人支付账户。
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="refund-form-row">
|
|
|
|
|
+ <NInput
|
|
|
|
|
+ v-model:value="refundReason"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ placeholder="请输入退单原因..."
|
|
|
|
|
+ :rows="2"
|
|
|
|
|
+ class="refund-reason-input"
|
|
|
|
|
+ />
|
|
|
|
|
+ <NButton
|
|
|
|
|
+ type="error"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ :loading="submitting"
|
|
|
|
|
+ :disabled="!refundReason || refundReason.trim() === ''"
|
|
|
|
|
+ @click="handleRefundConfirm"
|
|
|
|
|
+ >
|
|
|
|
|
+ 确认退单 · ¥{{ orderData.totalAmount }}
|
|
|
|
|
+ </NButton>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 状态时间线 -->
|
|
|
|
|
+ <div v-if="hasTimeInfo" class="timeline-section">
|
|
|
|
|
+ <div class="timeline-title">
|
|
|
|
|
+ 状态时间线
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="timeline-list">
|
|
|
|
|
+ <!-- 下单 -->
|
|
|
|
|
+ <div class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('create')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.createTime) }} · 顾客下单</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 支付 -->
|
|
|
|
|
+ <div v-if="orderData.payTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('pay')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.payTime) }} · 支付成功 · ¥{{ orderData.paidAmount || orderData.totalAmount }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 接单 -->
|
|
|
|
|
+ <div v-if="orderData.acceptTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('accept')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.acceptTime) }} · 餐厅前台接单</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 备餐 -->
|
|
|
|
|
+ <div v-if="orderData.prepareTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('prepare')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.prepareTime) }} · 开始备餐</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 出餐 -->
|
|
|
|
|
+ <div v-if="orderData.readyTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('ready')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.readyTime) }} · 出餐完成</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 配送 -->
|
|
|
|
|
+ <div v-if="orderData.deliverTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('deliver')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.deliverTime) }} · 开始配送</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 完成 -->
|
|
|
|
|
+ <div v-if="orderData.completeTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('complete')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.completeTime) }} · 配送完成</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 拒单 -->
|
|
|
|
|
+ <div v-if="orderData.rejectTime" class="timeline-item">
|
|
|
|
|
+ <span class="timeline-dot reject" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.rejectTime) }} · 已拒单{{ orderData.rejectReason ? ':' + orderData.rejectReason : '' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 退单申请 -->
|
|
|
|
|
+ <div v-if="orderData.refundApplyTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('refundApply')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.refundApplyTime) }} · 发起退单申请{{ orderData.refundReason ? ':' + orderData.refundReason : '' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 退单审核 -->
|
|
|
|
|
+ <div v-if="orderData.refundAuditTime" class="timeline-item">
|
|
|
|
|
+ <span :class="['timeline-dot', getDotClass('refundAudit')]" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.refundAuditTime) }} · 退单审核{{ orderData.status === 9 ? '通过' : '驳回' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 退款完成 -->
|
|
|
|
|
+ <div v-if="orderData.refundTime" class="timeline-item">
|
|
|
|
|
+ <span class="timeline-dot refund" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.refundTime) }} · 退款完成 · ¥{{ orderData.refundAmount || orderData.totalAmount }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 取消 -->
|
|
|
|
|
+ <div v-if="orderData.cancelTime" class="timeline-item">
|
|
|
|
|
+ <span class="timeline-dot cancel" />
|
|
|
|
|
+ <span class="timeline-text">{{ formatTime(orderData.cancelTime) }} · 订单已取消</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </NSpin>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { NButton, NInput, NSpin, useMessage } from 'naive-ui'
|
|
|
|
|
+import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
+import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
+import { getOrderDetail, hotelRefund } from '@/api/hotel'
|
|
|
|
|
+import DictTag from '@/components/DictTag.vue'
|
|
|
|
|
+import { useDict } from '@/composables/useDict'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({ name: 'HotelOrderRefund' })
|
|
|
|
|
+
|
|
|
|
|
+useDict('hotel_order_status', 'hotel_pay_method')
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+const route = useRoute()
|
|
|
|
|
+const message = useMessage()
|
|
|
|
|
+
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const orderData = ref(null)
|
|
|
|
|
+const refundReason = ref('')
|
|
|
|
|
+const submitting = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const hasTimeInfo = computed(() => {
|
|
|
|
|
+ if (!orderData.value)
|
|
|
|
|
+ return false
|
|
|
|
|
+ const d = orderData.value
|
|
|
|
|
+ return d.payTime || d.acceptTime || d.prepareTime || d.readyTime || d.deliverTime
|
|
|
|
|
+ || d.completeTime || d.rejectTime || d.refundApplyTime || d.refundAuditTime
|
|
|
|
|
+ || d.refundTime || d.cancelTime
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 根据步骤类型和订单状态返回圆点样式类
|
|
|
|
|
+ * - 终端态(拒单/退款完成/取消):固定颜色
|
|
|
|
|
+ * - 正常流程中:已完成=绿色,当前进行中=黄色
|
|
|
|
|
+ */
|
|
|
|
|
+function getDotClass(stepType) {
|
|
|
|
|
+ if (!orderData.value)
|
|
|
|
|
+ return ''
|
|
|
|
|
+ const status = orderData.value.status
|
|
|
|
|
+
|
|
|
|
|
+ // 终端态:始终用固定颜色
|
|
|
|
|
+ if (stepType === 'reject')
|
|
|
|
|
+ return 'reject'
|
|
|
|
|
+ if (stepType === 'cancel')
|
|
|
|
|
+ return 'cancel'
|
|
|
|
|
+
|
|
|
|
|
+ // 正常流程 + 退单流程:判断该步骤是否已完成
|
|
|
|
|
+ // 步骤对应的状态值
|
|
|
|
|
+ const stepStatusMap = {
|
|
|
|
|
+ create: 0,
|
|
|
|
|
+ pay: 1,
|
|
|
|
|
+ accept: 2,
|
|
|
|
|
+ prepare: 3,
|
|
|
|
|
+ ready: 4,
|
|
|
|
|
+ deliver: 5,
|
|
|
|
|
+ complete: 6,
|
|
|
|
|
+ refundApply: 8,
|
|
|
|
|
+ refundAudit: 9,
|
|
|
|
|
+ }
|
|
|
|
|
+ const stepStatus = stepStatusMap[stepType]
|
|
|
|
|
+ if (stepStatus === undefined)
|
|
|
|
|
+ return ''
|
|
|
|
|
+
|
|
|
|
|
+ // 如果订单状态已超过该步骤 → 已完成 → 绿色
|
|
|
|
|
+ if (status > stepStatus)
|
|
|
|
|
+ return 'completed'
|
|
|
|
|
+
|
|
|
|
|
+ // 否则是当前进行中的步骤 → 黄色
|
|
|
|
|
+ return 'active'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatTime(timeStr) {
|
|
|
|
|
+ if (!timeStr)
|
|
|
|
|
+ return ''
|
|
|
|
|
+ const match = String(timeStr).match(/(\d{2}:\d{2})/)
|
|
|
|
|
+ return match ? match[1] : timeStr
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadOrderDetail() {
|
|
|
|
|
+ const id = route.query.id
|
|
|
|
|
+ if (!id) {
|
|
|
|
|
+ message.error('缺少订单ID')
|
|
|
|
|
+ router.back()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getOrderDetail(id)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ orderData.value = res.data
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ catch {
|
|
|
|
|
+ message.error('加载订单详情失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleBack() {
|
|
|
|
|
+ router.back()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function handleRefundConfirm() {
|
|
|
|
|
+ if (!refundReason.value || refundReason.value.trim() === '') {
|
|
|
|
|
+ message.warning('请输入退单原因')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await hotelRefund(orderData.value.id, refundReason.value.trim())
|
|
|
|
|
+ message.success('退单成功')
|
|
|
|
|
+ router.back()
|
|
|
|
|
+ }
|
|
|
|
|
+ catch {
|
|
|
|
|
+ // 退款失败时订单不会变为已退单,错误详情已由请求拦截器统一弹窗展示
|
|
|
|
|
+ }
|
|
|
|
|
+ finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadOrderDetail()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.order-refund-page {
|
|
|
|
|
+ padding: 20px 24px;
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-title {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-subtitle {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.refund-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== 信息卡片 ==================== */
|
|
|
|
|
+.info-card {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding-bottom: 12px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.room-name {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.order-meta {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-info-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 32px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ padding-bottom: 12px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-label {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-value {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-value.amount {
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #f0a020;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== 菜品明细 ==================== */
|
|
|
|
|
+.items-section {
|
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.items-title {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.item-row {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ padding: 4px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.item-spec {
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.items-total {
|
|
|
|
|
+ border-top: 1px dashed #e0e0e0;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ padding-top: 8px;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== 退单操作 ==================== */
|
|
|
|
|
+.refund-action-card {
|
|
|
|
|
+ background: #fff5f5;
|
|
|
|
|
+ border: 1px solid #ffd0d0;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.refund-warning-title {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #d03050;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.refund-warning-text {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #d03050;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.refund-form-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.refund-reason-input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ==================== 时间线 ==================== */
|
|
|
|
|
+.timeline-section {
|
|
|
|
|
+ padding: 4px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-title {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding-left: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-dot {
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #f0a020;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-dot.completed {
|
|
|
|
|
+ background: #18a058;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-dot.active {
|
|
|
|
|
+ background: #f0a020;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-dot.reject {
|
|
|
|
|
+ background: #f0a020;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-dot.refund {
|
|
|
|
|
+ background: #d03050;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-dot.cancel {
|
|
|
|
|
+ background: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.timeline-text {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|