perf: 完善订单管理
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common'
|
||||
import { MembershipStatus, BookingStatus, UserRole } from '@mp-pilates/shared'
|
||||
import type { PaginatedData, UserProfileResponse, UserStatsResponse } from '@mp-pilates/shared'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import type { UserProfileResponse, UserStatsResponse } from '@mp-pilates/shared'
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
@@ -117,4 +117,89 @@ export class UserService {
|
||||
monthHours,
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Admin: paginated member list ─────────────────────────────────────────
|
||||
|
||||
async getMembers(
|
||||
page: number,
|
||||
limit: number,
|
||||
search?: string,
|
||||
): Promise<PaginatedData<{
|
||||
userId: string
|
||||
openid: string
|
||||
nickname: string
|
||||
phone: string | null
|
||||
avatarUrl: string | null
|
||||
totalBookings: number
|
||||
completedBookings: number
|
||||
cancelledBookings: number
|
||||
}>> {
|
||||
const where = search
|
||||
? {
|
||||
OR: [
|
||||
{ nickname: { contains: search, mode: 'insensitive' as const } },
|
||||
{ openid: { contains: search, mode: 'insensitive' as const } },
|
||||
{ phone: { contains: search } },
|
||||
],
|
||||
}
|
||||
: {}
|
||||
|
||||
const [users, total] = await Promise.all([
|
||||
this.prisma.user.findMany({
|
||||
where,
|
||||
select: {
|
||||
id: true,
|
||||
openid: true,
|
||||
nickname: true,
|
||||
phone: true,
|
||||
avatarUrl: true,
|
||||
_count: {
|
||||
select: {
|
||||
bookings: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
}),
|
||||
this.prisma.user.count({ where }),
|
||||
])
|
||||
|
||||
// Batch-fetch booking stats for the page of users
|
||||
const userIds = users.map((u) => u.id)
|
||||
|
||||
const bookingStats = userIds.length
|
||||
? await this.prisma.booking.groupBy({
|
||||
by: ['userId', 'status'],
|
||||
where: { userId: { in: userIds } },
|
||||
_count: { id: true },
|
||||
})
|
||||
: []
|
||||
|
||||
const statsMap = new Map<string, { total: number; completed: number; cancelled: number }>()
|
||||
for (const stat of bookingStats) {
|
||||
const entry = statsMap.get(stat.userId) ?? { total: 0, completed: 0, cancelled: 0 }
|
||||
entry.total += stat._count.id
|
||||
if (stat.status === BookingStatus.COMPLETED) entry.completed += stat._count.id
|
||||
if (stat.status === BookingStatus.CANCELLED) entry.cancelled += stat._count.id
|
||||
statsMap.set(stat.userId, entry)
|
||||
}
|
||||
|
||||
const items = users.map((u) => {
|
||||
const s = statsMap.get(u.id) ?? { total: 0, completed: 0, cancelled: 0 }
|
||||
return {
|
||||
userId: u.id,
|
||||
openid: u.openid,
|
||||
nickname: u.nickname,
|
||||
phone: u.phone,
|
||||
avatarUrl: u.avatarUrl,
|
||||
totalBookings: s.total,
|
||||
completedBookings: s.completed,
|
||||
cancelledBookings: s.cancelled,
|
||||
}
|
||||
})
|
||||
|
||||
return { items, total, page, limit }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user