fix: 修复订单列表不能查看的问题

This commit is contained in:
richarjiang
2026-04-10 23:07:56 +08:00
parent 54e30da003
commit 0810f71250
4 changed files with 142 additions and 37 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<view class="page" :style="{ paddingTop: navBarHeight }"> <view class="page" :style="{ '--status-bar': statusBarHeight + 'px' }">
<CustomNavBar title="订单管理" show-back /> <CustomNavBar title="订单管理" show-back />
<!-- Summary stats bar --> <!-- Summary stats bar -->
@@ -106,14 +106,14 @@
</view> </view>
<view class="info-right"> <view class="info-right">
<text class="info-label">下单时间</text> <text class="info-label">下单时间</text>
<text class="info-value">{{ formatDate(order.createdAt) }}</text> <text class="info-value">{{ formatDateTime(order.createdAt) }}</text>
</view> </view>
</view> </view>
<!-- Paid time if available --> <!-- Paid time if available -->
<view v-if="order.paidAt && order.status === OrderStatus.PAID" class="info-row"> <view v-if="order.paidAt && order.status === OrderStatus.PAID" class="info-row">
<text class="info-label">支付时间</text> <text class="info-label">支付时间</text>
<text class="info-value">{{ formatDate(order.paidAt) }}</text> <text class="info-value">{{ formatDateTime(order.paidAt) }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -133,19 +133,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import CustomNavBar from '../../components/CustomNavBar.vue' import CustomNavBar from '../../components/CustomNavBar.vue'
import { getSystemLayout } from '../../utils/system'
import { useAdminStore } from '../../stores/admin' import { useAdminStore } from '../../stores/admin'
import { formatPrice, formatDate } from '../../utils/format' import { formatPrice, formatDateTime } from '../../utils/format'
import { OrderStatus } from '@mp-pilates/shared' import { OrderStatus } from '@mp-pilates/shared'
import type { OrderWithDetails } from '@mp-pilates/shared' import type { OrderWithDetails } from '@mp-pilates/shared'
const adminStore = useAdminStore() const adminStore = useAdminStore()
const navBarHeight = ref('64px') // 动态计算顶部模块高度
const statusBarHeight = ref(0)
onMounted(() => { onMounted(() => {
navBarHeight.value = `${getSystemLayout().navBarHeight}px` const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight ?? 20
}) })
const filters = [ const filters = [
@@ -165,6 +167,21 @@ const totalCount = ref<number | null>(null)
const paidCount = ref<number | null>(null) const paidCount = ref<number | null>(null)
const pendingCount = ref<number | null>(null) const pendingCount = ref<number | null>(null)
// 每个 tab 单独缓存数据
const orderCache: Record<string, { items: OrderWithDetails[]; total: number; page: number; hasMore: boolean }> = {}
function getCacheKey(filter: string): string {
return filter || 'all'
}
function getCachedData(filter: string) {
return orderCache[getCacheKey(filter)]
}
function setCachedData(filter: string, data: { items: OrderWithDetails[]; total: number; page: number; hasMore: boolean }) {
orderCache[getCacheKey(filter)] = data
}
const LIMIT = 20 const LIMIT = 20
function statusLabel(s: string) { function statusLabel(s: string) {
@@ -191,23 +208,45 @@ function statusAccentClass(s: string) {
} }
async function loadOrders(reset = false) { async function loadOrders(reset = false) {
const filter = activeFilter.value
// 如果有缓存且是重置切换tab直接用缓存数据
if (reset) {
const cached = getCachedData(filter)
if (cached) {
orders.value = [...cached.items]
hasMore.value = cached.hasMore
page.value = cached.page
return
}
}
// 初始加载或下拉刷新,需要请求接口
if (loading.value) return if (loading.value) return
if (reset) page.value = 1 if (reset) page.value = 1
loading.value = true loading.value = true
try { try {
const params: { page: number; limit: number; status?: string } = { const params: { page: number; limit: number; status?: string } = {
page: page.value, page: page.value,
limit: LIMIT, limit: LIMIT,
} }
if (activeFilter.value) params.status = activeFilter.value if (filter) params.status = filter
const result = await adminStore.fetchAdminOrders(params) const result = await adminStore.fetchAdminOrders(params)
if (reset) {
orders.value = [...result.items] const newItems = reset ? [...result.items] : [...orders.value, ...result.items]
} else { const newHasMore = newItems.length < result.total
orders.value.push(...result.items)
} // 缓存数据
hasMore.value = orders.value.length < result.total setCachedData(filter, {
totalCount.value = result.total items: newItems,
total: result.total,
page: page.value,
hasMore: newHasMore,
})
orders.value = newItems
hasMore.value = newHasMore
} catch { } catch {
uni.showToast({ title: '加载失败', icon: 'none' }) uni.showToast({ title: '加载失败', icon: 'none' })
} finally { } finally {
@@ -216,30 +255,79 @@ async function loadOrders(reset = false) {
} }
} }
async function loadSummaryCounts() { // 初始加载所有分类的数据
async function loadAllFiltersData() {
loading.value = true
try { try {
const [allResult, paidResult, pendingResult] = await Promise.all([ // 并行请求所有分类(第一页数据)
adminStore.fetchAdminOrders({ page: 1, limit: 1 }), const [allResult, paidResult, pendingResult, refundedResult] = await Promise.all([
adminStore.fetchAdminOrders({ page: 1, limit: 1, status: OrderStatus.PAID }), adminStore.fetchAdminOrders({ page: 1, limit: LIMIT }),
adminStore.fetchAdminOrders({ page: 1, limit: 1, status: OrderStatus.PENDING }), adminStore.fetchAdminOrders({ page: 1, limit: LIMIT, status: OrderStatus.PAID }),
adminStore.fetchAdminOrders({ page: 1, limit: LIMIT, status: OrderStatus.PENDING }),
adminStore.fetchAdminOrders({ page: 1, limit: LIMIT, status: OrderStatus.REFUNDED }),
]) ])
// 缓存全部
setCachedData('', {
items: [...allResult.items],
total: allResult.total,
page: 1,
hasMore: allResult.items.length < allResult.total,
})
totalCount.value = allResult.total totalCount.value = allResult.total
// 缓存已支付
setCachedData(OrderStatus.PAID, {
items: [...paidResult.items],
total: paidResult.total,
page: 1,
hasMore: paidResult.items.length < paidResult.total,
})
paidCount.value = paidResult.total paidCount.value = paidResult.total
// 缓存待支付
setCachedData(OrderStatus.PENDING, {
items: [...pendingResult.items],
total: pendingResult.total,
page: 1,
hasMore: pendingResult.items.length < pendingResult.total,
})
pendingCount.value = pendingResult.total pendingCount.value = pendingResult.total
// 缓存已退款
setCachedData(OrderStatus.REFUNDED, {
items: [...refundedResult.items],
total: refundedResult.total,
page: 1,
hasMore: refundedResult.items.length < refundedResult.total,
})
// 设置当前 tab 的数据
orders.value = [...allResult.items]
hasMore.value = allResult.items.length < allResult.total
} catch { } catch {
// non-critical, ignore uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
refreshing.value = false
} }
} }
function selectFilter(value: string) { function selectFilter(value: string) {
activeFilter.value = value activeFilter.value = value
totalCount.value = null // 切换 tab 直接从缓存读取
loadOrders(true) const cached = getCachedData(value)
if (cached) {
orders.value = [...cached.items]
hasMore.value = cached.hasMore
page.value = cached.page
}
} }
async function onRefresh() { async function onRefresh() {
refreshing.value = true refreshing.value = true
await Promise.all([loadOrders(true), loadSummaryCounts()]) // 下拉刷新重新请求所有分类的数据
await loadAllFiltersData()
} }
function loadMore() { function loadMore() {
@@ -249,8 +337,7 @@ function loadMore() {
} }
onMounted(() => { onMounted(() => {
loadOrders(true) loadAllFiltersData()
loadSummaryCounts()
}) })
</script> </script>
@@ -266,13 +353,18 @@ onMounted(() => {
/* ── Stats bar ──────────────────────────────── */ /* ── Stats bar ──────────────────────────────── */
.stats-bar { .stats-bar {
position: fixed;
top: calc(var(--status-bar) + 44px);
left: 0;
right: 0;
display: flex; display: flex;
align-items: center; align-items: center;
height: 96rpx;
background: #FFFFFF; background: #FFFFFF;
padding: 28rpx 0; padding: 0;
margin: 0;
border-bottom: 1rpx solid rgba(180, 160, 130, 0.2); border-bottom: 1rpx solid rgba(180, 160, 130, 0.2);
box-shadow: 0 2rpx 12rpx rgba(180, 160, 130, 0.08); box-shadow: 0 2rpx 12rpx rgba(180, 160, 130, 0.08);
z-index: 100;
} }
.stat-item { .stat-item {
@@ -309,9 +401,13 @@ onMounted(() => {
/* ── Filter pills ───────────────────────────── */ /* ── Filter pills ───────────────────────────── */
.filter-wrap { .filter-wrap {
position: fixed;
top: calc(var(--status-bar) + 92px);
left: 0;
right: 0;
background: #FAF8F5; background: #FAF8F5;
border-bottom: 1rpx solid rgba(180, 160, 130, 0.15); border-bottom: 1rpx solid rgba(180, 160, 130, 0.15);
flex-shrink: 0; z-index: 99;
} }
.filter-scroll { overflow: hidden; } .filter-scroll { overflow: hidden; }
@@ -364,7 +460,11 @@ onMounted(() => {
/* ── List ───────────────────────────────────── */ /* ── List ───────────────────────────────────── */
.list-scroll { .list-scroll {
flex: 1; position: fixed;
top: calc(var(--status-bar) + 144px);
left: 0;
right: 0;
bottom: 0;
overflow: hidden; overflow: hidden;
} }

View File

@@ -120,7 +120,10 @@ export const useAdminStore = defineStore('admin', () => {
limit?: number limit?: number
status?: string status?: string
}): Promise<PaginatedData<OrderWithDetails>> { }): Promise<PaginatedData<OrderWithDetails>> {
return get<PaginatedData<OrderWithDetails>>('/admin/orders', params) console.log('[admin] fetchAdminOrders params:', params)
const result = await get<PaginatedData<OrderWithDetails>>('/admin/orders', params)
console.log('[admin] fetchAdminOrders result:', result)
return result
} }
// ── Bookings ───────────────────────────────────────────────────── // ── Bookings ─────────────────────────────────────────────────────

View File

@@ -135,14 +135,15 @@ export function getStockPercent(soldCount: number, totalStock: number): string {
return `${Math.min(100, getStockRatio(soldCount, totalStock) * 100)}%` return `${Math.min(100, getStockRatio(soldCount, totalStock) * 100)}%`
} }
/** 格式化日期时间为 MM-DD HH:mm */ /** 格式化日期时间为 MM-DD HH:mm:ss */
export function formatDateTime(dateStr: string): string { export function formatDateTime(dateStr: string): string {
const d = new Date(dateStr) const d = new Date(dateStr)
const month = String(d.getMonth() + 1).padStart(2, '0') const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0')
const hour = String(d.getHours()).padStart(2, '0') const hour = String(d.getHours()).padStart(2, '0')
const min = String(d.getMinutes()).padStart(2, '0') const min = String(d.getMinutes()).padStart(2, '0')
return `${month}-${day} ${hour}:${min}` const sec = String(d.getSeconds()).padStart(2, '0')
return `${month}-${day} ${hour}:${min}:${sec}`
} }
/** 格式化 Date 为 YYYY-MM-DD本地时间用于 picker */ /** 格式化 Date 为 YYYY-MM-DD本地时间用于 picker */

View File

@@ -15,7 +15,7 @@ export interface CreateOrderResult {
} }
export interface PaginatedOrders<T> { export interface PaginatedOrders<T> {
data: T[] items: T[]
total: number total: number
page: number page: number
limit: number limit: number
@@ -176,7 +176,7 @@ export class PaymentService {
]) ])
return { return {
data: data.map((o) => ({ ...o, cardType: { ...o.cardType } })), items: data.map((o) => ({ ...o, cardType: { ...o.cardType } })),
total, total,
page, page,
limit, limit,
@@ -207,8 +207,9 @@ export class PaymentService {
this.prisma.order.count({ where }), this.prisma.order.count({ where }),
]) ])
this.logger.log(`getAllOrders: page=${page}, limit=${limit}, status=${status}, count=${total}`)
return { return {
data: data.map((o) => ({ items: data.map((o) => ({
...o, ...o,
cardType: { ...o.cardType }, cardType: { ...o.cardType },
user: { ...o.user }, user: { ...o.user },