员工端h5版本代码提交
This commit is contained in:
234
src/views/orders/OrderDetailView.vue
Normal file
234
src/views/orders/OrderDetailView.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { CalendarDays, CheckCircle2, MapPin, Phone, ReceiptText, UserRound } from 'lucide-vue-next'
|
||||
import { fetchOrderDetail } from '@/api/orders'
|
||||
import PageNav from '@/components/PageNav.vue'
|
||||
import StatusTag from '@/components/StatusTag.vue'
|
||||
import type { UserOrderDetail } from '@/types/order'
|
||||
import { canWriteOff, commodityTypeText, payStatusText } from '@/utils/constants'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const detail = ref<UserOrderDetail | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const orderId = computed(() => String(route.params.id || ''))
|
||||
const packageName = computed(() => {
|
||||
const first = detail.value?.commodityPackageConfig?.[0]
|
||||
return first?.packageName || first?.name || detail.value?.commodityName || ''
|
||||
})
|
||||
|
||||
const loadDetail = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
detail.value = await fetchOrderDetail(orderId.value)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goWriteOff = () => {
|
||||
router.push({
|
||||
path: '/verify/confirm',
|
||||
query: {
|
||||
orderId: orderId.value,
|
||||
packageName: packageName.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(loadDetail)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageNav title="订单详情" />
|
||||
|
||||
<section class="page detail-page">
|
||||
<van-loading v-if="loading" />
|
||||
|
||||
<template v-else-if="detail">
|
||||
<section class="panel order-hero">
|
||||
<img :src="detail.commodityCoverPhoto" alt="" />
|
||||
<div class="order-hero__content">
|
||||
<div class="meta-row">
|
||||
<span>{{ detail.orderId }}</span>
|
||||
<StatusTag :status="detail.orderStatus" />
|
||||
</div>
|
||||
<h1>{{ detail.commodityName }}</h1>
|
||||
<div class="meta-row">
|
||||
<span>{{ commodityTypeText[detail.orderType] }} x {{ detail.commodityAmount }}</span>
|
||||
<strong>¥{{ detail.payAmt }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel detail-panel">
|
||||
<h2 class="section-title">联系人</h2>
|
||||
<div v-for="consumer in detail.consumerInfoList" :key="consumer.id || consumer.contactPhone" class="info-row">
|
||||
<UserRound :size="17" />
|
||||
<span>{{ consumer.visitorName || '未填写' }}</span>
|
||||
<strong>{{ consumer.contactPhone }}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel detail-panel">
|
||||
<h2 class="section-title">订单信息</h2>
|
||||
<div class="detail-row">
|
||||
<span>支付状态</span>
|
||||
<strong>{{ payStatusText[detail.payStatus] }}</strong>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span>订单金额</span>
|
||||
<strong>¥{{ detail.orderAmt }}</strong>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span>优惠金额</span>
|
||||
<strong>¥{{ detail.discountAmt || '0.00' }}</strong>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span>支付流水</span>
|
||||
<strong>{{ detail.paySerialNumber || '-' }}</strong>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span>下单时间</span>
|
||||
<strong>{{ detail.createTime || '-' }}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel detail-panel">
|
||||
<h2 class="section-title">使用信息</h2>
|
||||
<div class="info-row">
|
||||
<CalendarDays :size="17" />
|
||||
<span>预约时间</span>
|
||||
<strong>{{ detail.reservationDate || detail.checkInData || '无需预约' }}</strong>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<MapPin :size="17" />
|
||||
<span>核销地点</span>
|
||||
<strong>{{ detail.storeName || detail.commodityAddress || '-' }}</strong>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<Phone :size="17" />
|
||||
<span>投诉电话</span>
|
||||
<strong>{{ detail.complaintHotline || '-' }}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel detail-panel">
|
||||
<h2 class="section-title">套餐内容</h2>
|
||||
<div v-for="item in detail.commodityPackageConfig" :key="item.packageName || item.name" class="package-row">
|
||||
<ReceiptText :size="17" />
|
||||
<div>
|
||||
<strong>{{ item.packageName || item.name || detail.commodityName }}</strong>
|
||||
<p>{{ item.packageContent || item.packageDesc || item.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="detail.commodityTip" class="tip-text">{{ detail.commodityTip }}</p>
|
||||
</section>
|
||||
|
||||
<section class="panel detail-panel">
|
||||
<h2 class="section-title">核销记录</h2>
|
||||
<div v-if="detail.writeOffRecordList?.length">
|
||||
<div v-for="record in detail.writeOffRecordList" :key="record.id" class="record-row">
|
||||
<CheckCircle2 :size="18" />
|
||||
<div>
|
||||
<strong>{{ record.packageName || detail.commodityName }}</strong>
|
||||
<p>{{ record.createTime }} · {{ record.userName || '员工' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-line">暂无核销记录</div>
|
||||
</section>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<footer v-if="detail && canWriteOff(detail.orderStatus)" class="fixed-action">
|
||||
<div class="fixed-action__inner">
|
||||
<van-button block type="primary" @click="goWriteOff">
|
||||
<template #icon><CheckCircle2 :size="18" /></template>
|
||||
立即核销
|
||||
</van-button>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.detail-page {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 86px;
|
||||
}
|
||||
|
||||
.order-hero {
|
||||
display: grid;
|
||||
grid-template-columns: 112px 1fr;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.order-hero img {
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
border-radius: var(--radius);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.order-hero h1 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin: 12px 0;
|
||||
color: var(--text-strong);
|
||||
font-size: 18px;
|
||||
line-height: 1.35;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.detail-panel .detail-row + .detail-row,
|
||||
.detail-panel .info-row + .info-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.info-row,
|
||||
.package-row,
|
||||
.record-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 9px;
|
||||
color: var(--text-main);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-row svg,
|
||||
.package-row svg,
|
||||
.record-row svg {
|
||||
flex: 0 0 auto;
|
||||
margin-top: 2px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.info-row span {
|
||||
flex: 1;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.info-row strong,
|
||||
.package-row strong,
|
||||
.record-row strong {
|
||||
color: var(--text-strong);
|
||||
}
|
||||
|
||||
.package-row p,
|
||||
.record-row p,
|
||||
.tip-text,
|
||||
.empty-line {
|
||||
margin: 5px 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
171
src/views/orders/OrderListView.vue
Normal file
171
src/views/orders/OrderListView.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Search, SlidersHorizontal, ClipboardList } from 'lucide-vue-next'
|
||||
import { fetchOrderList } from '@/api/orders'
|
||||
import StatusTag from '@/components/StatusTag.vue'
|
||||
import type { CommodityOrder, CommodityOrderSearch } from '@/types/order'
|
||||
import { commodityTypeText } from '@/utils/constants'
|
||||
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const finished = ref(false)
|
||||
const records = ref<CommodityOrder[]>([])
|
||||
const status = ref('')
|
||||
const keyword = ref('')
|
||||
const typeCode = ref('')
|
||||
const showFilter = ref(false)
|
||||
|
||||
const query = reactive<CommodityOrderSearch>({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const loadOrders = async (reset = false) => {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
query.pageNum = 1
|
||||
finished.value = false
|
||||
}
|
||||
try {
|
||||
const searchText = keyword.value.trim()
|
||||
const page = await fetchOrderList({
|
||||
...query,
|
||||
orderStatus: status.value || undefined,
|
||||
commodityTypeCode: typeCode.value || undefined,
|
||||
orderId: /^\d{12,}$/.test(searchText) ? searchText : undefined,
|
||||
contactPhone: /^1\d{10}$/.test(searchText) ? searchText : undefined
|
||||
})
|
||||
records.value = reset ? page.records : [...records.value, ...page.records]
|
||||
finished.value = records.value.length >= page.total || page.records.length === 0
|
||||
query.pageNum += 1
|
||||
} finally {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onRefresh = () => {
|
||||
refreshing.value = true
|
||||
loadOrders(true)
|
||||
}
|
||||
|
||||
watch([status, typeCode], () => loadOrders(true))
|
||||
|
||||
onMounted(() => loadOrders(true))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page">
|
||||
<header class="page-header">
|
||||
<p class="page-kicker">订单管理</p>
|
||||
<h1 class="page-title">订单列表</h1>
|
||||
</header>
|
||||
|
||||
<div class="toolbar">
|
||||
<van-search
|
||||
v-model="keyword"
|
||||
shape="round"
|
||||
placeholder="订单号 / 手机号"
|
||||
@search="loadOrders(true)"
|
||||
@clear="loadOrders(true)"
|
||||
>
|
||||
<template #left-icon><Search :size="16" /></template>
|
||||
</van-search>
|
||||
<van-button class="filter-button" plain type="primary" @click="showFilter = true">
|
||||
<template #icon><SlidersHorizontal :size="16" /></template>
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<van-tabs v-model:active="status" shrink>
|
||||
<van-tab title="全部" name="" />
|
||||
<van-tab title="待使用" name="2" />
|
||||
<van-tab title="待确认" name="1" />
|
||||
<van-tab title="退款中" name="4" />
|
||||
<van-tab title="已完成" name="6" />
|
||||
</van-tabs>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多订单" @load="loadOrders">
|
||||
<div v-if="records.length" class="order-list">
|
||||
<RouterLink v-for="order in records" :key="order.id" class="order-card" :to="`/orders/${order.id}`">
|
||||
<div class="order-card__main">
|
||||
<div class="thumb">
|
||||
<img :src="order.commodityCoverPhoto" alt="" />
|
||||
</div>
|
||||
<div class="stack">
|
||||
<div class="meta-row">
|
||||
<span>{{ order.id }}</span>
|
||||
<StatusTag :status="order.orderStatus" />
|
||||
</div>
|
||||
<h2 class="card-title">{{ order.commodityName }}</h2>
|
||||
<div class="meta-row">
|
||||
<span>{{ order.visitorName }} · {{ order.contactPhone }}</span>
|
||||
<strong>¥{{ order.payAmt }}</strong>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span>{{ commodityTypeText[order.orderType] }} x {{ order.commodityAmount }}</span>
|
||||
<span>{{ order.reservationDate || order.createTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div v-else-if="!loading" class="empty-state">
|
||||
<ClipboardList :size="30" />
|
||||
<div>暂无订单</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
|
||||
<van-popup v-model:show="showFilter" position="bottom" round>
|
||||
<div class="filter-sheet">
|
||||
<h2>商品类型</h2>
|
||||
<van-radio-group v-model="typeCode">
|
||||
<van-cell-group inset>
|
||||
<van-cell title="全部" clickable @click="typeCode = ''">
|
||||
<template #right-icon><van-radio name="" /></template>
|
||||
</van-cell>
|
||||
<van-cell title="酒店" clickable @click="typeCode = '0'">
|
||||
<template #right-icon><van-radio name="0" /></template>
|
||||
</van-cell>
|
||||
<van-cell title="门票" clickable @click="typeCode = '1'">
|
||||
<template #right-icon><van-radio name="1" /></template>
|
||||
</van-cell>
|
||||
<van-cell title="餐饮" clickable @click="typeCode = '2'">
|
||||
<template #right-icon><van-radio name="2" /></template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-radio-group>
|
||||
<van-button block type="primary" @click="showFilter = false">完成</van-button>
|
||||
</div>
|
||||
</van-popup>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter-button {
|
||||
width: 44px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.filter-sheet {
|
||||
padding: 16px 14px calc(16px + var(--safe-bottom));
|
||||
background: var(--app-bg);
|
||||
}
|
||||
|
||||
.filter-sheet h2 {
|
||||
margin: 0 0 12px;
|
||||
color: var(--text-strong);
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.filter-sheet .van-button {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user