| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- <template>
- <view class="page">
- <!-- 顶部导航 -->
- <view class="nav-bar">
- <view class="nav-back" @click="goBack">
- <text class="nav-arrow">‹</text>
- </view>
- <text class="nav-title">{{ isDetailMode ? '订单详情' : '退单管理' }}</text>
- </view>
- <!-- ==================== 退单列表模式 ==================== -->
- <view v-if="!isDetailMode" class="content">
- <view v-if="!refundOrders.length && !loading" class="empty-state">
- <text class="empty-icon"></text>
- <text class="empty-text">暂无退单记录</text>
- </view>
- <view v-for="order in refundOrders" :key="order.id" class="order-card" @click="openDetail(order)">
- <view class="oc-header">
- <view class="oc-room">
- <text class="oc-room-icon">🏠</text>
- <text class="oc-room-no">{{ order.roomNo || '--' }}</text>
- </view>
- <view class="oc-status" :class="statusTagClass(order.status)">
- <text>{{ statusLabel(order.status) }}</text>
- </view>
- </view>
- <text class="oc-items">{{ buildItemSummary(order) }}</text>
- <view class="oc-footer">
- <text class="oc-amount">¥{{ order.totalAmount }}</text>
- <text class="oc-meta">{{ formatShortTime(order.createTime) }} · {{ order.orderNo }}</text>
- </view>
- <view v-if="order.refundReason" class="oc-reason">
- <text class="oc-reason-label">退单原因:</text>
- <text class="oc-reason-text">{{ order.refundReason }}</text>
- </view>
- <view v-if="order.status === 8" class="oc-actions">
- <view class="btn btn--success" @click.stop="handleApprove(order)">同意退单</view>
- <view class="btn btn--error" @click.stop="handleReject(order)">驳回</view>
- </view>
- </view>
- <view v-if="loading" class="loading-more"><text>加载中...</text></view>
- </view>
- <!-- ==================== 订单详情模式 ==================== -->
- <scroll-view v-else class="content" scroll-y>
- <view v-if="loading" class="loading-state"><text>加载中...</text></view>
- <view v-if="orderData" class="detail-content">
- <!-- 订单信息卡片 -->
- <view class="info-card">
- <view class="card-header">
- <view>
- <text class="room-name">{{ orderData.roomNo }} {{ orderData.roomName || '' }}</text>
- <text class="order-meta">{{ orderData.orderNo }} · {{ formatShortTime(orderData.createTime) }}</text>
- </view>
- <view class="oc-status" :class="statusTagClass(orderData.status)">
- <text>{{ statusLabel(orderData.status) }}</text>
- </view>
- </view>
- <view class="card-info-grid">
- <view class="info-item">
- <text class="info-label">联系人</text>
- <text class="info-value">{{ orderData.contactName || '-' }}</text>
- </view>
- <view class="info-item">
- <text class="info-label">电话</text>
- <text class="info-value">{{ orderData.contactPhone || '-' }}</text>
- </view>
- <view class="info-item">
- <text class="info-label">支付方式</text>
- <text class="info-value">{{ payMethodLabel(orderData.payMethod) }}</text>
- </view>
- <view class="info-item">
- <text class="info-label">金额</text>
- <text class="info-value info-amount">¥{{ orderData.totalAmount }}</text>
- </view>
- </view>
- <!-- 菜品明细 -->
- <view class="items-section">
- <text class="items-title">菜品明细</text>
- <view v-for="(item, idx) in (orderData.items || [])" :key="idx" class="item-row">
- <text class="item-name">{{ item.dishName }} ×{{ item.quantity }} — ¥{{ item.subtotal }}</text>
- <text v-if="item.specDesc" class="item-spec">({{ item.specDesc }})</text>
- </view>
- <view class="items-total">
- <text>合计 ¥{{ orderData.totalAmount }} · 免配送费</text>
- </view>
- </view>
- </view>
- <!-- 退单操作区域 -->
- <view v-if="canRefund" class="refund-card">
- <text class="refund-title"> 退单操作</text>
- <text class="refund-desc">宾馆前台可在任何阶段发起退单。退款将原路返回至客人支付账户。</text>
- <textarea class="refund-input" v-model="refundReason" placeholder="请输入退单原因..." :maxlength="200" />
- <view class="refund-btn" @click="handleRefundConfirm">
- <text>确认退单 · ¥{{ orderData.totalAmount }}</text>
- </view>
- </view>
- <!-- 退单审核操作(退单待审核状态) -->
- <view v-if="orderData.status === 8" class="refund-card refund-card--audit">
- <text class="refund-title">⚠ 退单审核</text>
- <text class="refund-desc">顾客已发起退单申请,请选择处理方式。</text>
- <textarea class="refund-input" v-model="auditReason" placeholder="审核备注(选填)..." :maxlength="200" />
- <view class="audit-btns">
- <view class="btn btn--success btn--block" @click="handleApproveConfirm">同意退单</view>
- <view class="btn btn--error btn--block" @click="handleRejectConfirm">驳回退单</view>
- </view>
- </view>
- <!-- 状态时间线 -->
- <view v-if="hasTimeInfo" class="timeline-section">
- <text class="timeline-title">📋 状态时间线</text>
- <view class="timeline-list">
- <view class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('create')" />
- <text class="timeline-text">{{ formatTime(orderData.createTime) }} · 顾客下单</text>
- </view>
- <view v-if="orderData.payTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('pay')" />
- <text class="timeline-text">{{ formatTime(orderData.payTime) }} · 支付成功 · ¥{{ orderData.paidAmount || orderData.totalAmount }}</text>
- </view>
- <view v-if="orderData.acceptTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('accept')" />
- <text class="timeline-text">{{ formatTime(orderData.acceptTime) }} · 餐厅前台接单</text>
- </view>
- <view v-if="orderData.prepareTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('prepare')" />
- <text class="timeline-text">{{ formatTime(orderData.prepareTime) }} · 开始备餐</text>
- </view>
- <view v-if="orderData.readyTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('ready')" />
- <text class="timeline-text">{{ formatTime(orderData.readyTime) }} · 出餐完成</text>
- </view>
- <view v-if="orderData.deliverTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('deliver')" />
- <text class="timeline-text">{{ formatTime(orderData.deliverTime) }} · 开始配送</text>
- </view>
- <view v-if="orderData.completeTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('complete')" />
- <text class="timeline-text">{{ formatTime(orderData.completeTime) }} · 配送完成</text>
- </view>
- <view v-if="orderData.rejectTime" class="timeline-item">
- <view class="timeline-dot reject" />
- <text class="timeline-text">{{ formatTime(orderData.rejectTime) }} · 已拒单{{ orderData.rejectReason ? ':' + orderData.rejectReason : '' }}</text>
- </view>
- <view v-if="orderData.refundApplyTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('refundApply')" />
- <text class="timeline-text">{{ formatTime(orderData.refundApplyTime) }} · 发起退单申请{{ orderData.refundReason ? ':' + orderData.refundReason : '' }}</text>
- </view>
- <view v-if="orderData.refundAuditTime" class="timeline-item">
- <view class="timeline-dot" :class="getDotClass('refundAudit')" />
- <text class="timeline-text">{{ formatTime(orderData.refundAuditTime) }} · 退单审核{{ orderData.status === 9 ? '通过' : '驳回' }}</text>
- </view>
- <view v-if="orderData.refundTime" class="timeline-item">
- <view class="timeline-dot refund" />
- <text class="timeline-text">{{ formatTime(orderData.refundTime) }} · 退款完成 · ¥{{ orderData.refundAmount || orderData.totalAmount }}</text>
- </view>
- <view v-if="orderData.cancelTime" class="timeline-item">
- <view class="timeline-dot cancel" />
- <text class="timeline-text">{{ formatTime(orderData.cancelTime) }} · 订单已取消</text>
- </view>
- </view>
- </view>
- </view>
- </scroll-view>
- <!-- 底部 TabBar -->
- <view class="bottom-tab">
- <view class="tab-item" @click="goOverview">
- <text class="tab-icon">📋</text>
- <text class="tab-label">订单总览</text>
- </view>
- <view class="tab-item" :class="{ 'tab-item--active': !isDetailMode }" @click="goRefundList">
- <text class="tab-icon">💰</text>
- <text class="tab-label">退单管理</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { computed, onMounted, ref } from 'vue'
- import api from '@/api'
- defineOptions({ name: 'HotelOrderRefund' })
- // ==================== 路由参数 ====================
- const orderId = ref('')
- const isDetailMode = ref(false)
- // ==================== 数据 ====================
- const orderData = ref(null)
- const loading = ref(false)
- const refundOrders = ref([])
- const refundReason = ref('')
- const auditReason = ref('')
- // ==================== 工具 ====================
- function buildItemSummary(order) {
- if (!order.items || !order.items.length) return ''
- return order.items.map(i => i.dishName).join('、')
- }
- function formatShortTime(timeStr) {
- if (!timeStr) return ''
- const match = String(timeStr).match(/(\d{2}:\d{2})/)
- return match ? match[1] : timeStr
- }
- function formatTime(timeStr) {
- if (!timeStr) return ''
- const match = String(timeStr).match(/(\d{2}:\d{2})/)
- return match ? match[1] : timeStr
- }
- function payMethodLabel(method) {
- const map = { ALIPAY: '支付宝', WECHAT: '微信支付', MOCK: '模拟支付', CASH: '现金' }
- return map[method] || method || '-'
- }
- function statusLabel(s) {
- const map = { 0: '待支付', 1: '待接单', 2: '已接单', 3: '备餐中', 4: '已出餐', 5: '配送中', 6: '已完成', 7: '已拒单', 8: '退单审核中', 9: '已退单', 10: '已超时取消', 11: '已取消' }
- return map[s] || '未知'
- }
- function statusTagClass(s) {
- if (s === 1) return 'tag--pending'
- if (s >= 2 && s <= 3) return 'tag--preparing'
- if (s === 4) return 'tag--ready'
- if (s === 5) return 'tag--delivering'
- if (s === 6) return 'tag--done'
- if (s === 7) return 'tag--rejected'
- if (s === 8) return 'tag--refund'
- if (s === 9) return 'tag--refunded'
- return 'tag--default'
- }
- // 终态订单不允许退单
- const canRefund = computed(() => {
- if (!orderData.value) return false
- const s = orderData.value.status
- return s !== 6 && s !== 7 && s !== 9 && s !== 10 && s !== 11
- })
- 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'
- }
- // ==================== 数据加载 ====================
- async function loadOrderDetail() {
- if (!orderId.value) return
- loading.value = true
- try {
- const res = await api.hotelStaffOrderDetail(orderId.value)
- if (res.code === 200) {
- orderData.value = res.data
- }
- } catch (e) {
- uni.showToast({ title: '加载订单详情失败', icon: 'none' })
- } finally {
- loading.value = false
- }
- }
- async function loadRefundOrders() {
- loading.value = true
- try {
- // 加载退单审核中(8) + 已退单(9) 的订单
- const [res8, res9] = await Promise.all([
- api.hotelStaffOrderPage({ pageNum: 1, pageSize: 50, status: 8 }),
- api.hotelStaffOrderPage({ pageNum: 1, pageSize: 50, status: 9 }),
- ])
- let list = []
- if (res8.code === 200 && res8.data?.records) list = list.concat(res8.data.records)
- if (res9.code === 200 && res9.data?.records) list = list.concat(res9.data.records)
- // 按创建时间倒序
- list.sort((a, b) => new Date(b.createTime) - new Date(a.createTime))
- refundOrders.value = list
- } catch (e) {
- console.error('[退单管理] 加载失败:', e)
- } finally {
- loading.value = false
- }
- }
- // ==================== 退单操作 ====================
- async function handleRefundConfirm() {
- if (!refundReason.value || refundReason.value.trim() === '') {
- uni.showToast({ title: '请输入退单原因', icon: 'none' }); return
- }
- try {
- await api.hotelHotelRefund(orderData.value.id, refundReason.value.trim())
- uni.showToast({ title: '退单成功', icon: 'success' })
- setTimeout(() => goBack(), 1500)
- } catch (e) {
- uni.showToast({ title: '退单失败', icon: 'none' })
- }
- }
- async function handleApproveConfirm() {
- uni.showModal({
- title: '同意退单',
- content: `确认同意订单 ${orderData.value.orderNo} 的退单申请?退款将原路返回。`,
- success: async (res) => {
- if (res.confirm) {
- try {
- await api.hotelApproveRefund(orderData.value.id, auditReason.value || undefined)
- uni.showToast({ title: '已同意退单', icon: 'success' })
- loadOrderDetail()
- } catch (e) {
- uni.showToast({ title: '操作失败', icon: 'none' })
- }
- }
- },
- })
- }
- async function handleRejectConfirm() {
- uni.showModal({
- title: '驳回退单',
- content: `确认驳回订单 ${orderData.value.orderNo} 的退单申请?`,
- success: async (res) => {
- if (res.confirm) {
- try {
- await api.hotelRejectRefund(orderData.value.id, auditReason.value || undefined)
- uni.showToast({ title: '已驳回退单', icon: 'success' })
- loadOrderDetail()
- } catch (e) {
- uni.showToast({ title: '操作失败', icon: 'none' })
- }
- }
- },
- })
- }
- // 列表页的审核操作
- function handleApprove(order) {
- uni.navigateTo({ url: `/pages/hotel/hotel-order-refund?id=${order.id}` })
- }
- function handleReject(order) {
- uni.navigateTo({ url: `/pages/hotel/hotel-order-refund?id=${order.id}` })
- }
- // ==================== 导航 ====================
- function openDetail(order) {
- uni.navigateTo({ url: `/pages/hotel/hotel-order-refund?id=${order.id}` })
- }
- function goBack() {
- uni.navigateBack({ fail: () => { uni.navigateTo({ url: '/pages/hotel/hotel-order' }) } })
- }
- function goOverview() {
- uni.navigateTo({ url: '/pages/hotel/hotel-order' })
- }
- function goRefundList() {
- if (isDetailMode.value) {
- uni.redirectTo({ url: '/pages/hotel/hotel-order-refund?mode=refund' })
- }
- }
- // ==================== 生命周期 ====================
- onMounted(() => {
- // 获取页面参数
- const pages = getCurrentPages()
- const currentPage = pages[pages.length - 1]
- const options = currentPage.$page?.options || currentPage.options || {}
- orderId.value = options.id || ''
- isDetailMode.value = !!orderId.value
- if (isDetailMode.value) {
- loadOrderDetail()
- } else {
- loadRefundOrders()
- }
- })
- </script>
- <style lang="scss" scoped>
- .page {
- min-height: 100vh;
- background: #f0f2f5;
- display: flex;
- flex-direction: column;
- padding-bottom: 120rpx;
- }
- /* ==================== 顶部导航 ==================== */
- .nav-bar {
- background: #1a365d;
- padding: 20rpx 24rpx 24rpx;
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
- .nav-back {
- width: 56rpx;
- height: 56rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .nav-arrow {
- font-size: 44rpx;
- color: #fff;
- font-weight: 300;
- }
- .nav-title {
- font-size: 32rpx;
- font-weight: 700;
- color: #fff;
- }
- /* ==================== 内容区 ==================== */
- .content {
- flex: 1;
- padding: 20rpx;
- }
- /* ==================== 订单卡片(列表模式) ==================== */
- .order-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 16rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
- }
- .oc-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12rpx;
- }
- .oc-room {
- display: flex;
- align-items: center;
- gap: 8rpx;
- }
- .oc-room-icon { font-size: 32rpx; }
- .oc-room-no { font-size: 32rpx; font-weight: 700; color: #1a1a1a; }
- .oc-status {
- padding: 6rpx 16rpx;
- border-radius: 8rpx;
- font-size: 22rpx;
- font-weight: 600;
- }
- .tag--pending { background: #fff0f0; color: #d03050; }
- .tag--preparing { background: #fff8e6; color: #f0a020; }
- .tag--ready { background: #e8f0fe; color: #2080f0; }
- .tag--delivering { background: #e8f8ee; color: #18a058; }
- .tag--done { background: #f0f0f0; color: #999; }
- .tag--rejected { background: #f0f0f0; color: #999; }
- .tag--refund { background: #fff3e0; color: #e67e22; }
- .tag--refunded { background: #f0f0f0; color: #999; }
- .tag--default { background: #f0f0f0; color: #999; }
- .oc-items {
- font-size: 26rpx;
- color: #666;
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- margin-bottom: 12rpx;
- }
- .oc-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .oc-amount { font-size: 30rpx; font-weight: 700; color: #f0a020; }
- .oc-meta { font-size: 22rpx; color: #bbb; }
- .oc-reason {
- margin-top: 12rpx;
- padding: 12rpx 16rpx;
- background: #fff8f8;
- border-radius: 8rpx;
- display: flex;
- gap: 8rpx;
- }
- .oc-reason-label { font-size: 22rpx; color: #999; }
- .oc-reason-text { font-size: 22rpx; color: #d03050; }
- .oc-actions {
- display: flex;
- gap: 16rpx;
- margin-top: 16rpx;
- justify-content: flex-end;
- }
- /* ==================== 按钮 ==================== */
- .btn {
- padding: 12rpx 28rpx;
- border-radius: 8rpx;
- font-size: 24rpx;
- font-weight: 600;
- text-align: center;
- }
- .btn--success { background: #e8f8ee; color: #18a058; }
- .btn--error { background: #fff0f0; color: #d03050; }
- .btn--block { width: 100%; padding: 20rpx; font-size: 28rpx; }
- /* ==================== 详情卡片 ==================== */
- .info-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 16rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- padding-bottom: 16rpx;
- border-bottom: 1rpx solid #f0f0f0;
- margin-bottom: 16rpx;
- }
- .room-name {
- font-size: 32rpx;
- font-weight: 700;
- color: #1a1a1a;
- display: block;
- }
- .order-meta {
- font-size: 22rpx;
- color: #999;
- margin-top: 6rpx;
- display: block;
- }
- .card-info-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- padding-bottom: 16rpx;
- border-bottom: 1rpx solid #f0f0f0;
- margin-bottom: 16rpx;
- }
- .info-item {
- width: calc(50% - 10rpx);
- display: flex;
- flex-direction: column;
- gap: 6rpx;
- }
- .info-label { font-size: 22rpx; color: #999; }
- .info-value { font-size: 26rpx; color: #333; }
- .info-amount { font-weight: 700; color: #f0a020; font-size: 28rpx; }
- /* 菜品明细 */
- .items-section {
- background: #f8f9fa;
- border-radius: 12rpx;
- padding: 16rpx 20rpx;
- }
- .items-title {
- font-size: 24rpx;
- color: #999;
- margin-bottom: 12rpx;
- display: block;
- }
- .item-row {
- padding: 6rpx 0;
- }
- .item-name { font-size: 26rpx; color: #333; }
- .item-spec { font-size: 22rpx; color: #999; margin-left: 8rpx; }
- .items-total {
- border-top: 1rpx dashed #e0e0e0;
- margin-top: 12rpx;
- padding-top: 12rpx;
- font-size: 28rpx;
- font-weight: 700;
- color: #333;
- }
- /* ==================== 退单操作 ==================== */
- .refund-card {
- background: #fff5f5;
- border: 1rpx solid #ffd0d0;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 16rpx;
- }
- .refund-card--audit {
- background: #fff8f0;
- border-color: #ffe0b2;
- }
- .refund-title {
- font-size: 28rpx;
- font-weight: 700;
- color: #d03050;
- display: block;
- margin-bottom: 8rpx;
- }
- .refund-desc {
- font-size: 24rpx;
- color: #d03050;
- display: block;
- margin-bottom: 16rpx;
- line-height: 1.5;
- }
- .refund-input {
- width: 100%;
- min-height: 120rpx;
- background: #fff;
- border: 1rpx solid #ffd0d0;
- border-radius: 8rpx;
- padding: 16rpx;
- font-size: 26rpx;
- color: #333;
- box-sizing: border-box;
- margin-bottom: 16rpx;
- }
- .refund-btn {
- background: #d03050;
- border-radius: 12rpx;
- padding: 24rpx;
- text-align: center;
- }
- .refund-btn text {
- color: #fff;
- font-size: 28rpx;
- font-weight: 700;
- }
- .audit-btns {
- display: flex;
- gap: 16rpx;
- }
- /* ==================== 时间线 ==================== */
- .timeline-section {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
- }
- .timeline-title {
- font-size: 28rpx;
- font-weight: 700;
- color: #1a1a1a;
- display: block;
- margin-bottom: 20rpx;
- }
- .timeline-list {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
- padding-left: 8rpx;
- }
- .timeline-item {
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
- .timeline-dot {
- width: 16rpx;
- height: 16rpx;
- 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: 24rpx;
- color: #666;
- }
- /* ==================== 空状态 / 加载 ==================== */
- .empty-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 120rpx 0;
- gap: 16rpx;
- }
- .empty-icon { font-size: 80rpx; }
- .empty-text { font-size: 28rpx; color: #bbb; }
- .loading-state, .loading-more {
- text-align: center;
- padding: 60rpx 0;
- font-size: 26rpx;
- color: #bbb;
- }
- /* ==================== 底部 TabBar ==================== */
- .bottom-tab {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: 100rpx;
- background: #fff;
- border-top: 1rpx solid #eee;
- display: flex;
- z-index: 100;
- }
- .tab-item {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 4rpx;
- }
- .tab-item--active .tab-label {
- color: #1a365d;
- font-weight: 600;
- }
- .tab-icon { font-size: 36rpx; }
- .tab-label { font-size: 22rpx; color: #999; }
- </style>
|