统一订单列表混排租赁与撞车,后台撞车订单展示真实手机号
PC/移动端「我的订单」并行拉取两类订单并按时间混排展示;后台撞车订单买家手机号不再脱敏,便于履约联系。
This commit is contained in:
@@ -102,8 +102,9 @@ func toOrderDTO(row model.MohongOrder, snap productSnapshot, items []orderItemSn
|
|||||||
UpdatedAt: row.UpdatedAt,
|
UpdatedAt: row.UpdatedAt,
|
||||||
}
|
}
|
||||||
if includeAdmin {
|
if includeAdmin {
|
||||||
|
// 后台展示真实手机号,便于履约联系买家
|
||||||
dto.BuyerNickname = buyerNickname
|
dto.BuyerNickname = buyerNickname
|
||||||
dto.BuyerPhone = maskPhone(buyerPhone)
|
dto.BuyerPhone = strings.TrimSpace(buyerPhone)
|
||||||
dto.AdminRemark = row.AdminRemark
|
dto.AdminRemark = row.AdminRemark
|
||||||
} else {
|
} else {
|
||||||
dto.BuyerNickname = buyerNickname
|
dto.BuyerNickname = buyerNickname
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ const { unreadCount, unreadLabel } = useChatUnreadCount(route)
|
|||||||
|
|
||||||
function isNavActive(path: string) {
|
function isNavActive(path: string) {
|
||||||
if (path === '/m') return route.path === '/m'
|
if (path === '/m') return route.path === '/m'
|
||||||
|
// 订单 Tab 同时覆盖租赁订单与撞车订单路径
|
||||||
|
if (path === '/m/orders') {
|
||||||
|
return route.path.startsWith('/m/orders') || route.path.startsWith('/m/crash/orders')
|
||||||
|
}
|
||||||
return route.path.startsWith(path)
|
return route.path.startsWith(path)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ async function handleSupportClick() {
|
|||||||
>
|
>
|
||||||
{{ supportLoading ? '...' : '客服' }}
|
{{ supportLoading ? '...' : '客服' }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="orders-btn" @click="router.push('/crash/orders')">订单</button>
|
<button type="button" class="orders-btn" @click="router.push('/m/orders')">订单</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="shop-body">
|
<div class="shop-body">
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ async function loadOrders() {
|
|||||||
:key="item.id"
|
:key="item.id"
|
||||||
type="button"
|
type="button"
|
||||||
class="card"
|
class="card"
|
||||||
@click="router.push(`/crash/orders/${item.id}`)"
|
@click="router.push(`/m/crash/orders/${item.id}`)"
|
||||||
>
|
>
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<span>{{ item.order_no }}</span>
|
<span>{{ item.order_no }}</span>
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ async function handleSupportClick() {
|
|||||||
<el-icon><ShoppingCart /></el-icon>
|
<el-icon><ShoppingCart /></el-icon>
|
||||||
<span>购物车{{ cartCount > 0 ? `(${cartCount})` : '' }}</span>
|
<span>购物车{{ cartCount > 0 ? `(${cartCount})` : '' }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="toolbar-btn outline" @click="router.push('/crash/orders')">
|
<button type="button" class="toolbar-btn outline" @click="router.push('/orders')">
|
||||||
<el-icon><Tickets /></el-icon>
|
<el-icon><Tickets /></el-icon>
|
||||||
<span>我的订单</span>
|
<span>我的订单</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ async function copyText() {
|
|||||||
<h1>{{ statusText }}</h1>
|
<h1>{{ statusText }}</h1>
|
||||||
<p class="sub">订单号 {{ order.order_no }} · {{ formatDateTime(order.created_at) }}</p>
|
<p class="sub">订单号 {{ order.order_no }} · {{ formatDateTime(order.created_at) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="toolbar-btn outline" @click="router.push('/crash/orders')">
|
<button type="button" class="toolbar-btn outline" @click="router.push('/orders')">
|
||||||
返回列表
|
返回列表
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { readError } from '@/shared/utils/error'
|
import { readError } from '@/shared/utils/error'
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, ref, computed, watch } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
||||||
@@ -11,16 +11,40 @@ import MobilePayWaySelectPopup from '@/features/orders/components/MobilePayWaySe
|
|||||||
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
import { useMobilePaymentCashier } from '@/features/orders/composables/useMobilePaymentCashier'
|
||||||
import { useMobilePayWaySelect } from '@/features/orders/composables/useMobilePayWaySelect'
|
import { useMobilePayWaySelect } from '@/features/orders/composables/useMobilePayWaySelect'
|
||||||
import { fetchOrderChat } from '@/features/chats/api/chats'
|
import { fetchOrderChat } from '@/features/chats/api/chats'
|
||||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
import {
|
||||||
|
fetchMyMohongOrders,
|
||||||
|
mohongOrderStatusLabel,
|
||||||
|
type MohongOrder,
|
||||||
|
} from '@/features/mohong/api/mohong'
|
||||||
|
import { centToYuan, formatCent, formatMoney } from '@/shared/utils/money'
|
||||||
import { useSessionStore } from '@/stores/session'
|
import { useSessionStore } from '@/stores/session'
|
||||||
import { formatDateMinute } from '@/shared/utils/time'
|
import { formatDateMinute } from '@/shared/utils/time'
|
||||||
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
||||||
|
|
||||||
|
type BizType = 'rental' | 'crash'
|
||||||
|
|
||||||
|
interface UnifiedOrderItem {
|
||||||
|
key: string
|
||||||
|
bizType: BizType
|
||||||
|
id: number
|
||||||
|
orderNo: string
|
||||||
|
title: string
|
||||||
|
status: string
|
||||||
|
createdAt: string
|
||||||
|
coverUrl?: string
|
||||||
|
quantity?: number
|
||||||
|
unit?: string
|
||||||
|
amountText: string
|
||||||
|
rental?: Order
|
||||||
|
crash?: MohongOrder
|
||||||
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const session = useSessionStore()
|
const session = useSessionStore()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const orders = ref<Order[]>([])
|
const rentalOrders = ref<Order[]>([])
|
||||||
|
const crashOrders = ref<MohongOrder[]>([])
|
||||||
const activeTab = ref('all')
|
const activeTab = ref('all')
|
||||||
const payingOrderId = ref<number | null>(null)
|
const payingOrderId = ref<number | null>(null)
|
||||||
const {
|
const {
|
||||||
@@ -34,45 +58,113 @@ const {
|
|||||||
refreshPaymentStatus,
|
refreshPaymentStatus,
|
||||||
} = useMobilePaymentCashier()
|
} = useMobilePaymentCashier()
|
||||||
|
|
||||||
// 支付方式选择底部弹层。
|
|
||||||
const payWaySelect = useMobilePayWaySelect()
|
const payWaySelect = useMobilePayWaySelect()
|
||||||
const selectPayWay = payWaySelect.select
|
const selectPayWay = payWaySelect.select
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadOrders()
|
|
||||||
if (route.query.tab) {
|
|
||||||
activeTab.value = String(route.query.tab)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function loadOrders() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const items = await fetchOrders()
|
|
||||||
orders.value = Array.isArray(items) ? items : []
|
|
||||||
} catch {
|
|
||||||
showToast({ message: '订单加载失败,请稍后重试', icon: 'warning-o' })
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 状态筛选 */
|
|
||||||
const statusTabs = [
|
const statusTabs = [
|
||||||
{ key: 'all', label: '全部' },
|
{ key: 'all', label: '全部' },
|
||||||
{ key: 'pending_payment', label: '待支付' },
|
{ key: 'pending_payment', label: '待支付' },
|
||||||
{ key: 'pending_handoff', label: '待交接' },
|
{ key: 'pending_handoff', label: '待交接' },
|
||||||
{ key: 'renting', label: '使用中' },
|
{ key: 'renting', label: '使用中' },
|
||||||
|
{ key: 'paid', label: '已支付' },
|
||||||
{ key: 'pending_checkout_confirm', label: '待结账' },
|
{ key: 'pending_checkout_confirm', label: '待结账' },
|
||||||
{ key: 'completed', label: '已完成' },
|
{ key: 'completed', label: '已完成' },
|
||||||
|
{ key: 'cancelled', label: '已取消' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const displayOrders = computed(() => {
|
const unifiedOrders = computed<UnifiedOrderItem[]>(() => {
|
||||||
if (activeTab.value === 'all') return orders.value
|
const rentalItems: UnifiedOrderItem[] = rentalOrders.value.map(order => ({
|
||||||
return orders.value.filter(o => o.status === activeTab.value)
|
key: `rental-${order.id}`,
|
||||||
|
bizType: 'rental',
|
||||||
|
id: order.id,
|
||||||
|
orderNo: order.order_no,
|
||||||
|
title: order.title,
|
||||||
|
status: order.status,
|
||||||
|
createdAt: order.created_at,
|
||||||
|
amountText: money(orderRentAmount(order)),
|
||||||
|
rental: order,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const crashItems: UnifiedOrderItem[] = crashOrders.value.map(order => ({
|
||||||
|
key: `crash-${order.id}`,
|
||||||
|
bizType: 'crash',
|
||||||
|
id: order.id,
|
||||||
|
orderNo: order.order_no,
|
||||||
|
title: order.product_title,
|
||||||
|
status: order.status,
|
||||||
|
createdAt: order.created_at,
|
||||||
|
coverUrl: order.product_cover_url,
|
||||||
|
quantity: order.quantity,
|
||||||
|
unit: order.product_unit,
|
||||||
|
amountText: order.amount || formatCent(order.amount_cent),
|
||||||
|
crash: order,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return [...rentalItems, ...crashItems].sort(
|
||||||
|
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function statusLabel(status: string) {
|
const displayOrders = computed(() => {
|
||||||
|
if (activeTab.value === 'all') return unifiedOrders.value
|
||||||
|
return unifiedOrders.value.filter(item => item.status === activeTab.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.tab) {
|
||||||
|
activeTab.value = String(route.query.tab)
|
||||||
|
}
|
||||||
|
loadOrders()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query.tab,
|
||||||
|
tab => {
|
||||||
|
if (tab) activeTab.value = String(tab)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function onStatusTabChange(name: string | number) {
|
||||||
|
activeTab.value = String(name)
|
||||||
|
router.replace({
|
||||||
|
path: '/m/orders',
|
||||||
|
query: {
|
||||||
|
tab: activeTab.value === 'all' ? undefined : activeTab.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOrders() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const [rentalResult, crashResult] = await Promise.allSettled([
|
||||||
|
fetchOrders(),
|
||||||
|
fetchMyMohongOrders({ page: 1, page_size: 50 }),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (rentalResult.status === 'fulfilled') {
|
||||||
|
rentalOrders.value = Array.isArray(rentalResult.value) ? rentalResult.value : []
|
||||||
|
} else {
|
||||||
|
rentalOrders.value = []
|
||||||
|
showToast({ message: '租赁订单加载失败', icon: 'warning-o' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crashResult.status === 'fulfilled') {
|
||||||
|
crashOrders.value = crashResult.value?.items || []
|
||||||
|
} else {
|
||||||
|
crashOrders.value = []
|
||||||
|
showToast({
|
||||||
|
message: readError(crashResult.reason, '撞车订单加载失败'),
|
||||||
|
icon: 'warning-o',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusLabel(item: UnifiedOrderItem) {
|
||||||
|
if (item.bizType === 'crash') return mohongOrderStatusLabel(item.status)
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
pending_payment: '待支付',
|
pending_payment: '待支付',
|
||||||
pending_handoff: '待交接',
|
pending_handoff: '待交接',
|
||||||
@@ -88,11 +180,19 @@ function statusLabel(status: string) {
|
|||||||
abnormal: '异常',
|
abnormal: '异常',
|
||||||
closed: '已关闭',
|
closed: '已关闭',
|
||||||
}
|
}
|
||||||
return map[status] || status
|
return map[item.status] || item.status
|
||||||
}
|
}
|
||||||
|
|
||||||
function goDetail(id: number) {
|
function bizTypeLabel(bizType: BizType) {
|
||||||
router.push(`/m/orders/${id}`)
|
return bizType === 'crash' ? '撞车' : '租赁'
|
||||||
|
}
|
||||||
|
|
||||||
|
function goDetail(item: UnifiedOrderItem) {
|
||||||
|
if (item.bizType === 'crash') {
|
||||||
|
router.push(`/m/crash/orders/${item.id}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push(`/m/orders/${item.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handlePay(order: Order) {
|
async function handlePay(order: Order) {
|
||||||
@@ -124,8 +224,6 @@ async function openOrderChatAfterPayment(order: Order) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectPayWay 在列表快捷支付前让用户选择支付宝或微信(见 payWaySelect)。
|
|
||||||
|
|
||||||
function money(value: unknown) {
|
function money(value: unknown) {
|
||||||
return formatMoney(Number(value || 0))
|
return formatMoney(Number(value || 0))
|
||||||
}
|
}
|
||||||
@@ -171,7 +269,6 @@ async function copyListingCode(order: Order) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="mobile-orders">
|
<main class="mobile-orders">
|
||||||
<!-- 顶部导航 -->
|
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<button class="back-btn" @click="router.back()">
|
<button class="back-btn" @click="router.back()">
|
||||||
<van-icon name="arrow-left" :size="20" />
|
<van-icon name="arrow-left" :size="20" />
|
||||||
@@ -180,7 +277,6 @@ async function copyListingCode(order: Order) {
|
|||||||
<span class="header-spacer"></span>
|
<span class="header-spacer"></span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- 状态筛选条 - Vant 滑动标签页 -->
|
|
||||||
<van-tabs
|
<van-tabs
|
||||||
v-model:active="activeTab"
|
v-model:active="activeTab"
|
||||||
class="custom-tabs"
|
class="custom-tabs"
|
||||||
@@ -192,11 +288,11 @@ async function copyListingCode(order: Order) {
|
|||||||
:border="false"
|
:border="false"
|
||||||
swipeable
|
swipeable
|
||||||
animated
|
animated
|
||||||
|
@change="onStatusTabChange"
|
||||||
>
|
>
|
||||||
<van-tab v-for="tab in statusTabs" :key="tab.key" :title="tab.label" :name="tab.key" />
|
<van-tab v-for="tab in statusTabs" :key="tab.key" :title="tab.label" :name="tab.key" />
|
||||||
</van-tabs>
|
</van-tabs>
|
||||||
|
|
||||||
<!-- 订单列表 -->
|
|
||||||
<section class="order-list">
|
<section class="order-list">
|
||||||
<van-loading v-if="loading" class="center-loading" size="24px" vertical>
|
<van-loading v-if="loading" class="center-loading" size="24px" vertical>
|
||||||
加载中...
|
加载中...
|
||||||
@@ -208,77 +304,124 @@ async function copyListingCode(order: Order) {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
v-for="order in displayOrders"
|
v-for="item in displayOrders"
|
||||||
:key="order.id"
|
:key="item.key"
|
||||||
class="order-card"
|
class="order-card"
|
||||||
@click="goDetail(order.id)"
|
@click="goDetail(item)"
|
||||||
>
|
>
|
||||||
<!-- 卡片头:订单号 + 状态 -->
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<span class="order-no">{{ order.order_no }}</span>
|
<span class="biz-tag" :class="item.bizType">{{ bizTypeLabel(item.bizType) }}</span>
|
||||||
|
<span class="order-no">{{ item.orderNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="status-badge" :class="'badge-' + order.status">
|
<span class="status-badge" :class="'badge-' + item.status">
|
||||||
{{ statusLabel(order.status) }}
|
{{ statusLabel(item) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 卡片体:关键信息 -->
|
<!-- 租赁卡片 -->
|
||||||
<div class="card-body">
|
<template v-if="item.bizType === 'rental' && item.rental">
|
||||||
<button class="listing-code-chip" type="button" @click.stop="copyListingCode(order)">
|
<div class="card-body">
|
||||||
商品编号 {{ formatListingCode(order) }}
|
<button
|
||||||
</button>
|
class="listing-code-chip"
|
||||||
<h3 class="order-title">{{ order.title }}</h3>
|
type="button"
|
||||||
<div class="tag-row">
|
@click.stop="copyListingCode(item.rental)"
|
||||||
<span class="info-tag">{{ order.server_region }}</span>
|
>
|
||||||
<span class="info-tag">{{ order.login_platform }}</span>
|
商品编号 {{ formatListingCode(item.rental) }}
|
||||||
|
</button>
|
||||||
|
<h3 class="order-title">{{ item.title }}</h3>
|
||||||
|
<div class="tag-row">
|
||||||
|
<span class="info-tag">{{ item.rental.server_region }}</span>
|
||||||
|
<span class="info-tag">{{ item.rental.login_platform }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="price-row">
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">{{ amountLabel(item.rental) }}</span>
|
||||||
|
<span class="price-val">¥{{ money(orderRentAmount(item.rental)) }}</span>
|
||||||
|
<span v-if="ownerActualIncome(item.rental) !== null" class="price-sub"
|
||||||
|
>实际到手 ¥{{ money(ownerActualIncome(item.rental)) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">押金金额</span>
|
||||||
|
<span class="price-val deposit">
|
||||||
|
¥{{ money(amountYuan(item.rental.deposit_amount_cent)) }}
|
||||||
|
<em v-if="amountYuan(item.rental.deposit_waived_amount_cent) > 0"
|
||||||
|
>免 ¥{{ money(amountYuan(item.rental.deposit_waived_amount_cent)) }}</em
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="price-row">
|
<div class="card-footer">
|
||||||
<div class="price-item">
|
<div class="time-box">
|
||||||
<span class="price-label">{{ amountLabel(order) }}</span>
|
<van-icon name="clock-o" class="clock-icon" />
|
||||||
<span class="price-val">¥{{ money(orderRentAmount(order)) }}</span>
|
<span class="order-time">{{ formatDateMinute(item.createdAt) }}</span>
|
||||||
<span v-if="ownerActualIncome(order) !== null" class="price-sub"
|
|
||||||
>实际到手 ¥{{ money(ownerActualIncome(order)) }}</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="price-item">
|
<div class="footer-action">
|
||||||
<span class="price-label">押金金额</span>
|
<van-button
|
||||||
<span class="price-val deposit">
|
v-if="
|
||||||
¥{{ money(amountYuan(order.deposit_amount_cent)) }}
|
item.rental.status === 'pending_payment' &&
|
||||||
<em v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
item.rental.renter_id === session.userId
|
||||||
>免 ¥{{ money(amountYuan(order.deposit_waived_amount_cent)) }}</em
|
"
|
||||||
>
|
size="small"
|
||||||
|
type="warning"
|
||||||
|
round
|
||||||
|
class="pay-btn"
|
||||||
|
:loading="payingOrderId === item.rental.id"
|
||||||
|
@click.stop="handlePay(item.rental)"
|
||||||
|
>
|
||||||
|
去支付
|
||||||
|
</van-button>
|
||||||
|
<span v-else class="detail-link">
|
||||||
|
查看详情 <van-icon name="arrow" :size="10" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
<!-- 卡片底:时间 + 操作 -->
|
<!-- 撞车卡片 -->
|
||||||
<div class="card-footer">
|
<template v-else>
|
||||||
<div class="time-box">
|
<div class="card-body crash-body">
|
||||||
<van-icon name="clock-o" class="clock-icon" />
|
<div class="crash-cover">
|
||||||
<span class="order-time">{{ formatDateMinute(order.created_at) }}</span>
|
<img v-if="item.coverUrl" :src="item.coverUrl" alt="" />
|
||||||
|
<span v-else>图</span>
|
||||||
|
</div>
|
||||||
|
<div class="crash-info">
|
||||||
|
<h3 class="order-title">{{ item.title }}</h3>
|
||||||
|
<p class="crash-meta">
|
||||||
|
x{{ item.quantity || 1 }}
|
||||||
|
<template v-if="item.unit"> · {{ item.unit }}</template>
|
||||||
|
</p>
|
||||||
|
<div class="price-row crash-price">
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">订单金额</span>
|
||||||
|
<span class="price-val">¥{{ item.amountText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-action">
|
|
||||||
<van-button
|
<div class="card-footer">
|
||||||
v-if="order.status === 'pending_payment' && order.renter_id === session.userId"
|
<div class="time-box">
|
||||||
size="small"
|
<van-icon name="clock-o" class="clock-icon" />
|
||||||
type="warning"
|
<span class="order-time">{{ formatDateMinute(item.createdAt) }}</span>
|
||||||
round
|
</div>
|
||||||
class="pay-btn"
|
<div class="footer-action">
|
||||||
:loading="payingOrderId === order.id"
|
<span v-if="item.status === 'pending_payment'" class="detail-link pay-hint">
|
||||||
@click.stop="handlePay(order)"
|
去支付 <van-icon name="arrow" :size="10" />
|
||||||
>
|
</span>
|
||||||
去支付
|
<span v-else class="detail-link">
|
||||||
</van-button>
|
查看详情 <van-icon name="arrow" :size="10" />
|
||||||
<span v-else class="detail-link"> 查看详情 <van-icon name="arrow" :size="10" /> </span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 底部导航 -->
|
|
||||||
<MobilePayWaySelectPopup
|
<MobilePayWaySelectPopup
|
||||||
v-model:show="payWaySelect.visible.value"
|
v-model:show="payWaySelect.visible.value"
|
||||||
@choose="payWaySelect.choose"
|
@choose="payWaySelect.choose"
|
||||||
@@ -306,7 +449,6 @@ async function copyListingCode(order: Order) {
|
|||||||
padding-bottom: calc(64px + env(safe-area-inset-bottom));
|
padding-bottom: calc(64px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 顶部导航 ========== */
|
|
||||||
.page-header {
|
.page-header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -344,7 +486,6 @@ async function copyListingCode(order: Order) {
|
|||||||
width: 36px;
|
width: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== Vant Tabs 自定义样式 ========== */
|
|
||||||
.custom-tabs {
|
.custom-tabs {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 48px;
|
top: 48px;
|
||||||
@@ -364,7 +505,6 @@ async function copyListingCode(order: Order) {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 订单列表 ========== */
|
|
||||||
.order-list {
|
.order-list {
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
}
|
}
|
||||||
@@ -379,7 +519,6 @@ async function copyListingCode(order: Order) {
|
|||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 订单卡片 ========== */
|
|
||||||
.order-card {
|
.order-card {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -406,15 +545,46 @@ async function copyListingCode(order: Order) {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
border-bottom: 1px dashed #f3f4f6;
|
border-bottom: 1px dashed #f3f4f6;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.biz-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.biz-tag.rental {
|
||||||
|
color: #1477ff;
|
||||||
|
background: rgba(20, 119, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.biz-tag.crash {
|
||||||
|
color: #ff6a00;
|
||||||
|
background: rgba(255, 106, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-no {
|
.order-no {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -433,7 +603,6 @@ async function copyListingCode(order: Order) {
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 状态徽章颜色 - 现代轻量化配色 */
|
|
||||||
.badge-pending_payment {
|
.badge-pending_payment {
|
||||||
color: #ff6a00;
|
color: #ff6a00;
|
||||||
background: rgba(255, 106, 0, 0.08);
|
background: rgba(255, 106, 0, 0.08);
|
||||||
@@ -450,10 +619,7 @@ async function copyListingCode(order: Order) {
|
|||||||
color: #ef4444;
|
color: #ef4444;
|
||||||
background: rgba(239, 68, 68, 0.08);
|
background: rgba(239, 68, 68, 0.08);
|
||||||
}
|
}
|
||||||
.badge-pending_return_confirm {
|
.badge-pending_return_confirm,
|
||||||
color: #8b5cf6;
|
|
||||||
background: rgba(139, 92, 246, 0.08);
|
|
||||||
}
|
|
||||||
.badge-pending_checkout_confirm {
|
.badge-pending_checkout_confirm {
|
||||||
color: #8b5cf6;
|
color: #8b5cf6;
|
||||||
background: rgba(139, 92, 246, 0.08);
|
background: rgba(139, 92, 246, 0.08);
|
||||||
@@ -462,30 +628,26 @@ async function copyListingCode(order: Order) {
|
|||||||
color: #a855f7;
|
color: #a855f7;
|
||||||
background: rgba(168, 85, 247, 0.08);
|
background: rgba(168, 85, 247, 0.08);
|
||||||
}
|
}
|
||||||
.badge-checkout_disputing {
|
.badge-checkout_disputing,
|
||||||
|
.badge-disputing,
|
||||||
|
.badge-abnormal {
|
||||||
color: #ef4444;
|
color: #ef4444;
|
||||||
background: rgba(239, 68, 68, 0.08);
|
background: rgba(239, 68, 68, 0.08);
|
||||||
}
|
}
|
||||||
|
.badge-paid {
|
||||||
|
color: #1477ff;
|
||||||
|
background: rgba(20, 119, 255, 0.08);
|
||||||
|
}
|
||||||
.badge-completed {
|
.badge-completed {
|
||||||
color: #10b981;
|
color: #10b981;
|
||||||
background: rgba(16, 185, 129, 0.08);
|
background: rgba(16, 185, 129, 0.08);
|
||||||
}
|
}
|
||||||
.badge-cancelled {
|
.badge-cancelled,
|
||||||
|
.badge-closed,
|
||||||
|
.badge-refunded {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
background: rgba(156, 163, 175, 0.08);
|
background: rgba(156, 163, 175, 0.08);
|
||||||
}
|
}
|
||||||
.badge-disputing {
|
|
||||||
color: #ef4444;
|
|
||||||
background: rgba(239, 68, 68, 0.08);
|
|
||||||
}
|
|
||||||
.badge-abnormal {
|
|
||||||
color: #ef4444;
|
|
||||||
background: rgba(239, 68, 68, 0.08);
|
|
||||||
}
|
|
||||||
.badge-closed {
|
|
||||||
color: #6b7280;
|
|
||||||
background: rgba(107, 114, 128, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
padding: 12px 0 0;
|
padding: 12px 0 0;
|
||||||
@@ -560,7 +722,49 @@ async function copyListingCode(order: Order) {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 卡片底部 ========== */
|
.crash-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 64px 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-cover {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f3f8;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #a0aec0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-cover img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-info {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-meta {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #8a94a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-price {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-price .price-val {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -594,6 +798,10 @@ async function copyListingCode(order: Order) {
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pay-hint {
|
||||||
|
color: #ff5f00;
|
||||||
|
}
|
||||||
|
|
||||||
.pay-btn {
|
.pay-btn {
|
||||||
height: 28px !important;
|
height: 28px !important;
|
||||||
padding: 0 16px !important;
|
padding: 0 16px !important;
|
||||||
|
|||||||
@@ -6,33 +6,93 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { CopyDocument, Search } from '@element-plus/icons-vue'
|
import { CopyDocument, Search } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
import { fetchOrders, type Order } from '@/features/orders'
|
import { fetchOrders, type Order } from '@/features/orders'
|
||||||
import { centToYuan, formatMoney } from '@/shared/utils/money'
|
import {
|
||||||
|
fetchMyMohongOrders,
|
||||||
|
mohongOrderStatusLabel,
|
||||||
|
type MohongOrder,
|
||||||
|
} from '@/features/mohong/api/mohong'
|
||||||
|
import { centToYuan, formatCent, formatMoney } from '@/shared/utils/money'
|
||||||
import { useSessionStore } from '@/stores/session'
|
import { useSessionStore } from '@/stores/session'
|
||||||
import { orderStatusLabel } from '@/shared/utils/statusLabels'
|
import { orderStatusLabel } from '@/shared/utils/statusLabels'
|
||||||
import { formatDateTime } from '@/shared/utils/time'
|
import { formatDateTime } from '@/shared/utils/time'
|
||||||
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
import { formatListingNo } from '@/shared/utils/listingDisplay'
|
||||||
|
|
||||||
|
type BizType = 'rental' | 'crash'
|
||||||
|
|
||||||
|
interface UnifiedOrderItem {
|
||||||
|
key: string
|
||||||
|
bizType: BizType
|
||||||
|
id: number
|
||||||
|
orderNo: string
|
||||||
|
title: string
|
||||||
|
status: string
|
||||||
|
createdAt: string
|
||||||
|
amountText: string
|
||||||
|
coverUrl?: string
|
||||||
|
quantity?: number
|
||||||
|
unit?: string
|
||||||
|
rental?: Order
|
||||||
|
crash?: MohongOrder
|
||||||
|
}
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const orders = ref<Order[]>([])
|
const rentalOrders = ref<Order[]>([])
|
||||||
|
const crashOrders = ref<MohongOrder[]>([])
|
||||||
const payingOrderId = ref<number | null>(null)
|
const payingOrderId = ref<number | null>(null)
|
||||||
const session = useSessionStore()
|
const session = useSessionStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
|
const fallbackPendingPaymentMinutes = 15
|
||||||
|
|
||||||
const statusTabs = [
|
const statusTabs = [
|
||||||
{ key: 'all', label: '全部' },
|
{ key: 'all', label: '全部' },
|
||||||
{ key: 'pending_payment', label: '待支付' },
|
{ key: 'pending_payment', label: '待支付' },
|
||||||
{ key: 'pending_handoff', label: '待交接' },
|
{ key: 'pending_handoff', label: '待交接' },
|
||||||
{ key: 'renting', label: '使用中' },
|
{ key: 'renting', label: '使用中' },
|
||||||
|
{ key: 'paid', label: '已支付' },
|
||||||
{ key: 'pending_checkout_confirm', label: '待结账' },
|
{ key: 'pending_checkout_confirm', label: '待结账' },
|
||||||
{ key: 'completed', label: '已完成' },
|
{ key: 'completed', label: '已完成' },
|
||||||
|
{ key: 'cancelled', label: '已取消' },
|
||||||
]
|
]
|
||||||
const tabKeys = new Set(statusTabs.map(item => item.key))
|
const tabKeys = new Set(statusTabs.map(item => item.key))
|
||||||
const activeTab = ref(readTab(route.query.tab))
|
const activeTab = ref(readTab(route.query.tab))
|
||||||
const fallbackPendingPaymentMinutes = 15
|
|
||||||
|
const unifiedOrders = computed<UnifiedOrderItem[]>(() => {
|
||||||
|
const rentalItems: UnifiedOrderItem[] = rentalOrders.value.map(order => ({
|
||||||
|
key: `rental-${order.id}`,
|
||||||
|
bizType: 'rental',
|
||||||
|
id: order.id,
|
||||||
|
orderNo: order.order_no,
|
||||||
|
title: order.title,
|
||||||
|
status: order.status,
|
||||||
|
createdAt: order.created_at,
|
||||||
|
amountText: money(orderRentAmount(order)),
|
||||||
|
rental: order,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const crashItems: UnifiedOrderItem[] = crashOrders.value.map(order => ({
|
||||||
|
key: `crash-${order.id}`,
|
||||||
|
bizType: 'crash',
|
||||||
|
id: order.id,
|
||||||
|
orderNo: order.order_no,
|
||||||
|
title: order.product_title,
|
||||||
|
status: order.status,
|
||||||
|
createdAt: order.created_at,
|
||||||
|
amountText: order.amount || formatCent(order.amount_cent),
|
||||||
|
coverUrl: order.product_cover_url,
|
||||||
|
quantity: order.quantity,
|
||||||
|
unit: order.product_unit,
|
||||||
|
crash: order,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return [...rentalItems, ...crashItems].sort(
|
||||||
|
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const displayOrders = computed(() => {
|
const displayOrders = computed(() => {
|
||||||
let filtered = orders.value
|
let filtered = unifiedOrders.value
|
||||||
|
|
||||||
if (activeTab.value !== 'all') {
|
if (activeTab.value !== 'all') {
|
||||||
filtered = filtered.filter(order => order.status === activeTab.value)
|
filtered = filtered.filter(order => order.status === activeTab.value)
|
||||||
@@ -40,24 +100,28 @@ const displayOrders = computed(() => {
|
|||||||
|
|
||||||
if (searchKeyword.value.trim()) {
|
if (searchKeyword.value.trim()) {
|
||||||
const keyword = searchKeyword.value.trim().toLowerCase()
|
const keyword = searchKeyword.value.trim().toLowerCase()
|
||||||
filtered = filtered.filter(
|
filtered = filtered.filter(order => {
|
||||||
order =>
|
if (textValue(order.orderNo).toLowerCase().includes(keyword)) return true
|
||||||
textValue(order.order_no).toLowerCase().includes(keyword) ||
|
if (textValue(order.title).toLowerCase().includes(keyword)) return true
|
||||||
formatListingCode(order).toLowerCase().includes(keyword) ||
|
if (order.bizType === 'rental' && order.rental) {
|
||||||
String(order.listing_id).includes(keyword) ||
|
return (
|
||||||
String(order.account_id).includes(keyword) ||
|
formatListingCode(order.rental).toLowerCase().includes(keyword) ||
|
||||||
textValue(order.title).toLowerCase().includes(keyword)
|
String(order.rental.listing_id).includes(keyword) ||
|
||||||
)
|
String(order.rental.account_id).includes(keyword)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabCounts = computed(() => {
|
const tabCounts = computed(() => {
|
||||||
const counts: Record<string, number> = { all: orders.value.length }
|
const counts: Record<string, number> = { all: unifiedOrders.value.length }
|
||||||
statusTabs.forEach(tab => {
|
statusTabs.forEach(tab => {
|
||||||
if (tab.key !== 'all') {
|
if (tab.key !== 'all') {
|
||||||
counts[tab.key] = orders.value.filter(order => order.status === tab.key).length
|
counts[tab.key] = unifiedOrders.value.filter(order => order.status === tab.key).length
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return counts
|
return counts
|
||||||
@@ -79,19 +143,44 @@ function readTab(tab: unknown) {
|
|||||||
|
|
||||||
function handleTabChange(tab: string | number) {
|
function handleTabChange(tab: string | number) {
|
||||||
const nextTab = readTab(String(tab))
|
const nextTab = readTab(String(tab))
|
||||||
|
activeTab.value = nextTab
|
||||||
router.replace({ path: '/orders', query: nextTab === 'all' ? {} : { tab: nextTab } })
|
router.replace({ path: '/orders', query: nextTab === 'all' ? {} : { tab: nextTab } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadOrders() {
|
async function loadOrders() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const items = await fetchOrders()
|
const [rentalResult, crashResult] = await Promise.allSettled([
|
||||||
orders.value = Array.isArray(items) ? items : []
|
fetchOrders(),
|
||||||
|
fetchMyMohongOrders({ page: 1, page_size: 50 }),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (rentalResult.status === 'fulfilled') {
|
||||||
|
rentalOrders.value = Array.isArray(rentalResult.value) ? rentalResult.value : []
|
||||||
|
} else {
|
||||||
|
rentalOrders.value = []
|
||||||
|
ElMessage.error('租赁订单加载失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crashResult.status === 'fulfilled') {
|
||||||
|
crashOrders.value = crashResult.value?.items || []
|
||||||
|
} else {
|
||||||
|
crashOrders.value = []
|
||||||
|
ElMessage.error(readError(crashResult.reason, '撞车订单加载失败'))
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goDetail(item: UnifiedOrderItem) {
|
||||||
|
if (item.bizType === 'crash') {
|
||||||
|
router.push(`/crash/orders/${item.id}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push(`/orders/${item.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePay(order: Order) {
|
async function handlePay(order: Order) {
|
||||||
payingOrderId.value = order.id
|
payingOrderId.value = order.id
|
||||||
try {
|
try {
|
||||||
@@ -202,6 +291,15 @@ function getCountdownMinutes(order: Order) {
|
|||||||
const diff = deadline.getTime() - now.getTime()
|
const diff = deadline.getTime() - now.getTime()
|
||||||
return Math.max(0, Math.ceil(diff / 60000))
|
return Math.max(0, Math.ceil(diff / 60000))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function statusText(item: UnifiedOrderItem) {
|
||||||
|
if (item.bizType === 'crash') return mohongOrderStatusLabel(item.status)
|
||||||
|
return orderStatusLabel(item.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bizTypeLabel(bizType: BizType) {
|
||||||
|
return bizType === 'crash' ? '撞车' : '租赁'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -209,13 +307,13 @@ function getCountdownMinutes(order: Order) {
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<p class="eyebrow">ORDERS</p>
|
<p class="eyebrow">ORDERS</p>
|
||||||
<h1>我的订单</h1>
|
<h1>我的订单</h1>
|
||||||
<p>跟踪待支付、待交接、使用中、待归还、申诉中和已完成订单。</p>
|
<p>租赁与撞车订单统一展示,按创建时间倒序排列。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="orders-toolbar">
|
<div class="orders-toolbar">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchKeyword"
|
v-model="searchKeyword"
|
||||||
placeholder="搜索订单号 / 商品编号 / 账号"
|
placeholder="搜索订单号 / 商品 / 账号"
|
||||||
:prefix-icon="Search"
|
:prefix-icon="Search"
|
||||||
clearable
|
clearable
|
||||||
class="search-input"
|
class="search-input"
|
||||||
@@ -241,89 +339,130 @@ function getCountdownMinutes(order: Order) {
|
|||||||
|
|
||||||
<div v-else class="orders-container">
|
<div v-else class="orders-container">
|
||||||
<el-table v-loading="loading" class="table-panel orders-table" :data="displayOrders">
|
<el-table v-loading="loading" class="table-panel orders-table" :data="displayOrders">
|
||||||
|
<el-table-column label="类型" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="biz-tag" :class="row.bizType">{{ bizTypeLabel(row.bizType) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="订单号" min-width="180">
|
<el-table-column label="订单号" min-width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="order-no-cell">
|
<div class="order-no-cell">
|
||||||
<span class="order-no-text" :title="row.order_no">{{
|
<span class="order-no-text" :title="row.orderNo">{{
|
||||||
shortenOrderNo(row.order_no)
|
shortenOrderNo(row.orderNo)
|
||||||
}}</span>
|
}}</span>
|
||||||
<el-icon class="copy-icon" @click="copyOrderNo(row.order_no)">
|
<el-icon class="copy-icon" @click="copyOrderNo(row.orderNo)">
|
||||||
<CopyDocument />
|
<CopyDocument />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="商品编号" width="150">
|
<el-table-column label="商品 / 账号" min-width="220">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="order-no-cell">
|
<div v-if="row.bizType === 'crash'" class="crash-product-cell">
|
||||||
<span class="order-no-text">{{ formatListingCode(row) }}</span>
|
<div class="crash-cover">
|
||||||
<el-icon class="copy-icon" @click="copyListingCode(row)">
|
<img v-if="row.coverUrl" :src="row.coverUrl" alt="" />
|
||||||
<CopyDocument />
|
<span v-else>图</span>
|
||||||
</el-icon>
|
</div>
|
||||||
|
<div class="crash-product-info">
|
||||||
|
<strong>{{ row.title }}</strong>
|
||||||
|
<span
|
||||||
|
>x{{ row.quantity || 1 }}{{ row.unit ? ` · ${row.unit}` : '' }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="rental-product-cell">
|
||||||
|
<button
|
||||||
|
v-if="row.rental"
|
||||||
|
class="listing-code-line compact"
|
||||||
|
type="button"
|
||||||
|
@click="copyListingCode(row.rental)"
|
||||||
|
>
|
||||||
|
{{ formatListingCode(row.rental) }}
|
||||||
|
<el-icon><CopyDocument /></el-icon>
|
||||||
|
</button>
|
||||||
|
<span class="rental-title">{{ row.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="title" label="账号" min-width="180" />
|
<el-table-column label="金额" width="140">
|
||||||
<el-table-column label="金额" width="132">
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="amount-cell">
|
<div v-if="row.bizType === 'rental' && row.rental" class="amount-cell">
|
||||||
<span class="amount-value">¥{{ money(orderRentAmount(row)) }}</span>
|
<span class="amount-value">¥{{ money(orderRentAmount(row.rental)) }}</span>
|
||||||
<span class="amount-label">{{ amountLabel(row) }}</span>
|
<span class="amount-label">{{ amountLabel(row.rental) }}</span>
|
||||||
<span v-if="ownerActualIncome(row) !== null" class="amount-sub"
|
<span v-if="ownerActualIncome(row.rental) !== null" class="amount-sub"
|
||||||
>实际到手 ¥{{ money(ownerActualIncome(row)) }}</span
|
>实际到手 ¥{{ money(ownerActualIncome(row.rental)) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="amount-cell">
|
||||||
|
<span class="amount-value">¥{{ row.amountText }}</span>
|
||||||
|
<span class="amount-label">订单金额</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="押金" width="100">
|
<el-table-column label="押金 / 数量" width="110">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="amount-cell">
|
<div v-if="row.bizType === 'rental' && row.rental" class="amount-cell">
|
||||||
<span class="amount-value">¥{{ money(amountYuan(row.deposit_amount_cent)) }}</span>
|
<span class="amount-value">¥{{ money(amountYuan(row.rental.deposit_amount_cent)) }}</span>
|
||||||
<span v-if="amountYuan(row.deposit_waived_amount_cent) > 0" class="amount-label"
|
<span v-if="amountYuan(row.rental.deposit_waived_amount_cent) > 0" class="amount-label"
|
||||||
>免 ¥{{ money(amountYuan(row.deposit_waived_amount_cent)) }}</span
|
>免 ¥{{ money(amountYuan(row.rental.deposit_waived_amount_cent)) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<span v-else class="amount-value"
|
||||||
|
>x{{ row.quantity || 1 }}{{ row.unit ? row.unit : '' }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="身份" width="80">
|
<el-table-column label="身份" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="isRenter(row) ? 'primary' : 'success'" size="small">
|
<el-tag
|
||||||
{{ orderRole(row) }}
|
v-if="row.bizType === 'rental' && row.rental"
|
||||||
|
:type="isRenter(row.rental) ? 'primary' : 'success'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ orderRole(row.rental) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
|
<span v-else class="muted">买家</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" width="140">
|
<el-table-column label="状态" width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="status-cell">
|
<div class="status-cell">
|
||||||
<span>{{ orderStatusLabel(row.status) }}</span>
|
<span>{{ statusText(row) }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="row.status === 'pending_payment' && getCountdownMinutes(row) > 0"
|
v-if="
|
||||||
|
row.bizType === 'rental' &&
|
||||||
|
row.rental &&
|
||||||
|
row.rental.status === 'pending_payment' &&
|
||||||
|
getCountdownMinutes(row.rental) > 0
|
||||||
|
"
|
||||||
class="countdown-badge"
|
class="countdown-badge"
|
||||||
>
|
>
|
||||||
{{ getCountdownMinutes(row) }}分钟后超时
|
{{ getCountdownMinutes(row.rental) }}分钟后超时
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="创建时间" width="160">
|
<el-table-column label="创建时间" width="160">
|
||||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="160" fixed="right">
|
<el-table-column label="操作" width="160" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.status === 'pending_payment'"
|
v-if="
|
||||||
|
row.bizType === 'rental' &&
|
||||||
|
row.rental &&
|
||||||
|
row.rental.status === 'pending_payment'
|
||||||
|
"
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="row.renter_id !== session.userId"
|
:disabled="row.rental.renter_id !== session.userId"
|
||||||
:loading="payingOrderId === row.id"
|
:loading="payingOrderId === row.rental.id"
|
||||||
@click="handlePay(row)"
|
@click="handlePay(row.rental)"
|
||||||
>
|
>
|
||||||
使用中
|
去支付
|
||||||
</el-button>
|
</el-button>
|
||||||
<RouterLink :to="`/orders/${row.id}`">
|
<el-button size="small" @click="goDetail(row)">详情</el-button>
|
||||||
<el-button size="small">详情</el-button>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -331,72 +470,115 @@ function getCountdownMinutes(order: Order) {
|
|||||||
|
|
||||||
<div class="mobile-cards">
|
<div class="mobile-cards">
|
||||||
<div
|
<div
|
||||||
v-for="order in displayOrders"
|
v-for="item in displayOrders"
|
||||||
:key="order.id"
|
:key="item.key"
|
||||||
class="order-card"
|
class="order-card"
|
||||||
@click="router.push(`/orders/${order.id}`)"
|
@click="goDetail(item)"
|
||||||
>
|
>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="order-no-row">
|
<div class="order-no-row">
|
||||||
<span class="order-no-text" :title="order.order_no">{{
|
<span class="biz-tag" :class="item.bizType">{{ bizTypeLabel(item.bizType) }}</span>
|
||||||
shortenOrderNo(order.order_no)
|
<span class="order-no-text" :title="item.orderNo">{{
|
||||||
|
shortenOrderNo(item.orderNo)
|
||||||
}}</span>
|
}}</span>
|
||||||
<el-icon class="copy-icon" @click.stop="copyOrderNo(order.order_no)">
|
<el-icon class="copy-icon" @click.stop="copyOrderNo(item.orderNo)">
|
||||||
<CopyDocument />
|
<CopyDocument />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<el-tag :type="isRenter(order) ? 'primary' : 'success'" size="small">
|
<el-tag
|
||||||
{{ orderRole(order) }}
|
v-if="item.bizType === 'rental' && item.rental"
|
||||||
</el-tag>
|
:type="isRenter(item.rental) ? 'primary' : 'success'"
|
||||||
</div>
|
|
||||||
<button class="listing-code-line" type="button" @click.stop="copyListingCode(order)">
|
|
||||||
商品编号 {{ formatListingCode(order) }}
|
|
||||||
<el-icon><CopyDocument /></el-icon>
|
|
||||||
</button>
|
|
||||||
<div class="card-title">{{ order.title }}</div>
|
|
||||||
<div class="card-meta">
|
|
||||||
<div class="meta-row">
|
|
||||||
<span class="meta-label">{{ amountLabel(order) }}</span>
|
|
||||||
<span class="meta-value amount">¥{{ money(orderRentAmount(order)) }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="ownerActualIncome(order) !== null" class="meta-row">
|
|
||||||
<span class="meta-label">实际到手</span>
|
|
||||||
<span class="meta-value amount">¥{{ money(ownerActualIncome(order)) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="meta-row">
|
|
||||||
<span class="meta-label">押金</span>
|
|
||||||
<span class="meta-value">
|
|
||||||
¥{{ money(amountYuan(order.deposit_amount_cent)) }}
|
|
||||||
<em v-if="amountYuan(order.deposit_waived_amount_cent) > 0"
|
|
||||||
>免 ¥{{ money(amountYuan(order.deposit_waived_amount_cent)) }}</em
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="meta-row">
|
|
||||||
<span class="meta-label">创建时间</span>
|
|
||||||
<span class="meta-value">{{ formatDateTime(order.created_at) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
<div class="status-info">
|
|
||||||
<span class="status-label">{{ orderStatusLabel(order.status) }}</span>
|
|
||||||
<span
|
|
||||||
v-if="order.status === 'pending_payment' && getCountdownMinutes(order) > 0"
|
|
||||||
class="countdown-badge"
|
|
||||||
>
|
|
||||||
{{ getCountdownMinutes(order) }}分钟后超时
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<el-button
|
|
||||||
v-if="order.status === 'pending_payment' && isRenter(order)"
|
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
|
||||||
:loading="payingOrderId === order.id"
|
|
||||||
@click.stop="handlePay(order)"
|
|
||||||
>
|
>
|
||||||
立即支付
|
{{ orderRole(item.rental) }}
|
||||||
</el-button>
|
</el-tag>
|
||||||
|
<span v-else class="crash-status" :class="item.status">{{ statusText(item) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template v-if="item.bizType === 'rental' && item.rental">
|
||||||
|
<button
|
||||||
|
class="listing-code-line"
|
||||||
|
type="button"
|
||||||
|
@click.stop="copyListingCode(item.rental)"
|
||||||
|
>
|
||||||
|
商品编号 {{ formatListingCode(item.rental) }}
|
||||||
|
<el-icon><CopyDocument /></el-icon>
|
||||||
|
</button>
|
||||||
|
<div class="card-title">{{ item.title }}</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">{{ amountLabel(item.rental) }}</span>
|
||||||
|
<span class="meta-value amount">¥{{ money(orderRentAmount(item.rental)) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="ownerActualIncome(item.rental) !== null" class="meta-row">
|
||||||
|
<span class="meta-label">实际到手</span>
|
||||||
|
<span class="meta-value amount">¥{{ money(ownerActualIncome(item.rental)) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">押金</span>
|
||||||
|
<span class="meta-value">
|
||||||
|
¥{{ money(amountYuan(item.rental.deposit_amount_cent)) }}
|
||||||
|
<em v-if="amountYuan(item.rental.deposit_waived_amount_cent) > 0"
|
||||||
|
>免 ¥{{ money(amountYuan(item.rental.deposit_waived_amount_cent)) }}</em
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">创建时间</span>
|
||||||
|
<span class="meta-value">{{ formatDateTime(item.createdAt) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="status-info">
|
||||||
|
<span class="status-label">{{ statusText(item) }}</span>
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
item.rental.status === 'pending_payment' && getCountdownMinutes(item.rental) > 0
|
||||||
|
"
|
||||||
|
class="countdown-badge"
|
||||||
|
>
|
||||||
|
{{ getCountdownMinutes(item.rental) }}分钟后超时
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
v-if="item.rental.status === 'pending_payment' && isRenter(item.rental)"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
:loading="payingOrderId === item.rental.id"
|
||||||
|
@click.stop="handlePay(item.rental)"
|
||||||
|
>
|
||||||
|
立即支付
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="crash-card-body">
|
||||||
|
<div class="crash-cover lg">
|
||||||
|
<img v-if="item.coverUrl" :src="item.coverUrl" alt="" />
|
||||||
|
<span v-else>图</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="card-title">{{ item.title }}</div>
|
||||||
|
<div class="card-meta">
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">数量</span>
|
||||||
|
<span class="meta-value"
|
||||||
|
>x{{ item.quantity || 1 }}{{ item.unit ? ` · ${item.unit}` : '' }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">金额</span>
|
||||||
|
<span class="meta-value amount">¥{{ item.amountText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">创建时间</span>
|
||||||
|
<span class="meta-value">{{ formatDateTime(item.createdAt) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -472,6 +654,27 @@ function getCountdownMinutes(order: Order) {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.biz-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.biz-tag.rental {
|
||||||
|
color: #1477ff;
|
||||||
|
background: rgba(20, 119, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.biz-tag.crash {
|
||||||
|
color: #ff6a00;
|
||||||
|
background: rgba(255, 106, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.order-no-cell {
|
.order-no-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -500,6 +703,11 @@ function getCountdownMinutes(order: Order) {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listing-code-line.compact {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.copy-icon {
|
.copy-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
@@ -554,6 +762,105 @@ function getCountdownMinutes(order: Order) {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-product-cell,
|
||||||
|
.rental-product-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-product-cell {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-cover {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f3f8;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #a0aec0;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-cover.lg {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-cover img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-product-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-product-info strong,
|
||||||
|
.rental-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-product-info span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8a94a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-status {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-status.pending_payment {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #ff6a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-status.paid {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-status.completed {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-status.cancelled,
|
||||||
|
.crash-status.refunded {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crash-card-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 64px 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.order-card {
|
.order-card {
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -580,6 +887,7 @@ function getCountdownMinutes(order: Order) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
|
|||||||
@@ -98,10 +98,10 @@ export const mobileRoutes: RouteRecordRaw[] = [
|
|||||||
meta: { layout: 'blank' },
|
meta: { layout: 'blank' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// 撞车订单列表统一到底栏「订单」页(与租赁订单混排)
|
||||||
path: '/m/crash/orders',
|
path: '/m/crash/orders',
|
||||||
name: 'mobile-crash-orders',
|
name: 'mobile-crash-orders',
|
||||||
component: () => import('@/features/mohong/views/MobileMohongOrdersView.vue'),
|
redirect: '/m/orders',
|
||||||
meta: { layout: 'blank', requiresAuth: true },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/m/crash/orders/:id',
|
path: '/m/crash/orders/:id',
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export const publicRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/features/mohong/views/MohongListView.vue'),
|
component: () => import('@/features/mohong/views/MohongListView.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// 撞车订单列表统一到「我的订单」页(与租赁订单混排)
|
||||||
path: '/crash/orders',
|
path: '/crash/orders',
|
||||||
name: 'crash-orders',
|
name: 'crash-orders',
|
||||||
component: () => import('@/features/mohong/views/MohongOrdersView.vue'),
|
redirect: '/orders',
|
||||||
meta: { requiresAuth: true },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/crash/orders/:id',
|
path: '/crash/orders/:id',
|
||||||
|
|||||||
Reference in New Issue
Block a user