|
|
@@ -1,24 +1,46 @@
|
|
|
<template>
|
|
|
<div class="admin-order-list">
|
|
|
- <h1>订单审核</h1>
|
|
|
+ <h1>订单管理</h1>
|
|
|
|
|
|
<div class="toolbar">
|
|
|
- <el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="fetchOrders">
|
|
|
- <el-option label="全部" value="" />
|
|
|
+ <el-select v-model="statusFilter" placeholder="状态筛选" clearable style="width: 140px">
|
|
|
+ <el-option label="全部状态" value="" />
|
|
|
<el-option label="待审核" value="PENDING" />
|
|
|
<el-option label="已确认" value="CONFIRMED" />
|
|
|
<el-option label="已取消" value="CANCELLED" />
|
|
|
<el-option label="取消申请" value="CANCEL_REQUESTED" />
|
|
|
<el-option label="已完成" value="COMPLETED" />
|
|
|
</el-select>
|
|
|
+ <el-select v-model="typeFilter" placeholder="类型筛选" clearable style="width: 140px; margin-left: 12px">
|
|
|
+ <el-option label="全部类型" value="" />
|
|
|
+ <el-option label="寄养" value="BOARDING" />
|
|
|
+ <el-option label="美容" value="GROOMING" />
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" style="margin-left: 12px" @click="handleSearch">搜索</el-button>
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
</div>
|
|
|
|
|
|
- <el-table :data="orders" border stripe v-loading="loading">
|
|
|
+ <el-table :data="orders" border stripe v-loading="loading" style="width: 100%">
|
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
|
- <el-table-column prop="userId" label="用户" width="120" />
|
|
|
- <el-table-column prop="type" label="类型" width="100" />
|
|
|
- <el-table-column prop="totalPrice" label="总价" width="100" />
|
|
|
- <el-table-column prop="status" label="状态" width="140">
|
|
|
+ <el-table-column label="用户" min-width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.userName || row.userId }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="petName" label="宠物" min-width="100" />
|
|
|
+ <el-table-column label="类型" min-width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ typeLabel(row.type) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="服务日期" min-width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.startDate?.replace('T', ' ')?.substring(0, 10) }} ~ {{ row.endDate?.replace('T', ' ')?.substring(0, 10) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="roomName" label="房间" min-width="100" />
|
|
|
+ <el-table-column prop="totalPrice" label="总价" min-width="100" />
|
|
|
+ <el-table-column prop="status" label="状态" min-width="120">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
|
|
</template>
|
|
|
@@ -47,8 +69,8 @@
|
|
|
<el-form>
|
|
|
<el-form-item label="审核结果">
|
|
|
<el-radio-group v-model="reviewAction">
|
|
|
- <el-radio value="CONFIRMED">通过</el-radio>
|
|
|
- <el-radio value="CANCELLED">拒绝</el-radio>
|
|
|
+ <el-radio value="APPROVE">通过</el-radio>
|
|
|
+ <el-radio value="REJECT">拒绝</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="原因">
|
|
|
@@ -67,8 +89,8 @@
|
|
|
<el-form>
|
|
|
<el-form-item label="处理结果">
|
|
|
<el-radio-group v-model="cancelAction">
|
|
|
- <el-radio value="CANCELLED">同意取消</el-radio>
|
|
|
- <el-radio value="CONFIRMED">拒绝取消</el-radio>
|
|
|
+ <el-radio value="AGREE">同意取消</el-radio>
|
|
|
+ <el-radio value="REJECT">拒绝取消</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="处理意见">
|
|
|
@@ -88,30 +110,92 @@ import { ref } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import type { Appointment } from '@/type'
|
|
|
+import { statusLabel, statusTag, typeLabel } from '@/type'
|
|
|
import { getAdminAppointmentController } from '@/api/admin-appointment-controller'
|
|
|
+import { getUserController } from '@/api/user-controller'
|
|
|
+import { getPetController } from '@/api/pet-controller'
|
|
|
+import { getBoardingRoomController } from '@/api/boarding-room-controller'
|
|
|
import type { ListAll1Params, ReviewDto, HandleCancelDto } from '@/api/models'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const orders = ref<Appointment[]>([])
|
|
|
const loading = ref(false)
|
|
|
const statusFilter = ref('')
|
|
|
+const typeFilter = ref('')
|
|
|
const pageNum = ref(1)
|
|
|
const pageSize = ref(10)
|
|
|
const total = ref(0)
|
|
|
|
|
|
// Review dialog
|
|
|
const reviewVisible = ref(false)
|
|
|
-const reviewAction = ref('CONFIRMED')
|
|
|
+const reviewAction = ref('APPROVE')
|
|
|
const reviewReason = ref('')
|
|
|
const reviewingOrder = ref<Appointment | null>(null)
|
|
|
|
|
|
// Cancel dialog
|
|
|
const cancelVisible = ref(false)
|
|
|
-const cancelAction = ref('CANCELLED')
|
|
|
+const cancelAction = ref('AGREE')
|
|
|
const cancelOpinion = ref('')
|
|
|
const cancelReason = ref('')
|
|
|
const cancelOrder = ref<Appointment | null>(null)
|
|
|
|
|
|
+const enrichOrders = async (list: Appointment[]) => {
|
|
|
+ if (!list.length) return
|
|
|
+ const userIds = [...new Set(list.map((o) => Number(o.userId)).filter(Boolean))]
|
|
|
+ const petIds = [...new Set(list.map((o) => Number(o.petId)).filter(Boolean))]
|
|
|
+ const roomIds = [
|
|
|
+ ...new Set(list.map((o) => (o.roomId ? Number(o.roomId) : null)).filter(Boolean))
|
|
|
+ ] as number[]
|
|
|
+
|
|
|
+ const userMap = new Map<number, string>()
|
|
|
+ if (userIds.length) {
|
|
|
+ const results = await Promise.allSettled(
|
|
|
+ userIds.map((id) => getUserController().queryById(id))
|
|
|
+ )
|
|
|
+ for (let i = 0; i < results.length; i++) {
|
|
|
+ const r = results[i]
|
|
|
+ if (r.status === 'fulfilled') {
|
|
|
+ const vo = r.value as { code?: number; data?: { username?: string }; message?: string }
|
|
|
+ if (vo.data?.username) userMap.set(userIds[i], vo.data.username)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const petMap = new Map<number, string>()
|
|
|
+ if (petIds.length) {
|
|
|
+ const results = await Promise.allSettled(
|
|
|
+ petIds.map((id) => getPetController().getById(id))
|
|
|
+ )
|
|
|
+ for (let i = 0; i < results.length; i++) {
|
|
|
+ const r = results[i]
|
|
|
+ if (r.status === 'fulfilled') {
|
|
|
+ const vo = r.value as { code?: number; data?: { name?: string }; message?: string }
|
|
|
+ if (vo.data?.name) petMap.set(petIds[i], vo.data.name)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const roomMap = new Map<number, string>()
|
|
|
+ if (roomIds.length) {
|
|
|
+ const res = await getBoardingRoomController().listRooms()
|
|
|
+ const body = res as { code?: number; data?: Array<{ id?: number; roomName?: string }>; message?: string }
|
|
|
+ if (body.code === 200 && body.data) {
|
|
|
+ body.data.forEach((r) => {
|
|
|
+ if (r.id != null && r.roomName) roomMap.set(r.id, r.roomName)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ list.forEach((o) => {
|
|
|
+ const uid = Number(o.userId)
|
|
|
+ if (userMap.has(uid)) o.userName = userMap.get(uid)!
|
|
|
+ const pid = Number(o.petId)
|
|
|
+ if (petMap.has(pid)) o.petName = petMap.get(pid)!
|
|
|
+ const rid = o.roomId ? Number(o.roomId) : null
|
|
|
+ if (rid != null && roomMap.has(rid)) o.roomName = roomMap.get(rid)!
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
const fetchOrders = async () => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
@@ -121,6 +205,7 @@ const fetchOrders = async () => {
|
|
|
pageSize: pageSize.value
|
|
|
}
|
|
|
if (statusFilter.value) params.status = statusFilter.value
|
|
|
+ if (typeFilter.value) params.type = typeFilter.value
|
|
|
const res = await api.listAll1(params)
|
|
|
const page = res.data
|
|
|
if (res.code === 200 && page) {
|
|
|
@@ -131,6 +216,9 @@ const fetchOrders = async () => {
|
|
|
petId: String(a.petId ?? ''),
|
|
|
packageId: String(a.packageId ?? ''),
|
|
|
roomId: a.roomId ? String(a.roomId) : undefined,
|
|
|
+ roomName: a.roomName,
|
|
|
+ petName: a.petName,
|
|
|
+ packageName: a.packageName,
|
|
|
type: (a.type as Appointment['type']) ?? 'GROOMING',
|
|
|
startDate: a.startDate,
|
|
|
endDate: a.endDate,
|
|
|
@@ -143,6 +231,7 @@ const fetchOrders = async () => {
|
|
|
createdAt: a.createdAt ?? ''
|
|
|
}))
|
|
|
total.value = page.total ?? 0
|
|
|
+ await enrichOrders(orders.value)
|
|
|
}
|
|
|
} catch (err: any) {
|
|
|
ElMessage.error(err.message || '加载失败')
|
|
|
@@ -153,7 +242,7 @@ const fetchOrders = async () => {
|
|
|
|
|
|
const openReview = (order: Appointment) => {
|
|
|
reviewingOrder.value = order
|
|
|
- reviewAction.value = 'CONFIRMED'
|
|
|
+ reviewAction.value = 'APPROVE'
|
|
|
reviewReason.value = ''
|
|
|
reviewVisible.value = true
|
|
|
}
|
|
|
@@ -164,7 +253,7 @@ const submitReview = async () => {
|
|
|
const api = getAdminAppointmentController()
|
|
|
const dto: ReviewDto = {
|
|
|
action: reviewAction.value,
|
|
|
- rejectReason: reviewAction.value === 'CANCELLED' ? reviewReason.value : undefined
|
|
|
+ rejectReason: reviewAction.value === 'REJECT' ? reviewReason.value : undefined
|
|
|
}
|
|
|
const res = await api.review(Number(reviewingOrder.value.id), dto)
|
|
|
if (res.code === 200) {
|
|
|
@@ -182,7 +271,7 @@ const submitReview = async () => {
|
|
|
const openCancelHandle = (order: Appointment) => {
|
|
|
cancelOrder.value = order
|
|
|
cancelReason.value = order.cancelReason || ''
|
|
|
- cancelAction.value = 'CANCELLED'
|
|
|
+ cancelAction.value = 'AGREE'
|
|
|
cancelOpinion.value = ''
|
|
|
cancelVisible.value = true
|
|
|
}
|
|
|
@@ -209,26 +298,16 @@ const viewDetail = (id: string) => {
|
|
|
router.push(`/admin/orders/${id}`)
|
|
|
}
|
|
|
|
|
|
-const statusLabel = (status: string) => {
|
|
|
- const map: Record<string, string> = {
|
|
|
- PENDING: '待审核',
|
|
|
- CONFIRMED: '已确认',
|
|
|
- CANCELLED: '已取消',
|
|
|
- COMPLETED: '已完成',
|
|
|
- CANCEL_REQUESTED: '取消申请'
|
|
|
- }
|
|
|
- return map[status] || status
|
|
|
+const handleSearch = () => {
|
|
|
+ pageNum.value = 1
|
|
|
+ fetchOrders()
|
|
|
}
|
|
|
|
|
|
-const statusTag = (status: string) => {
|
|
|
- const map: Record<string, string> = {
|
|
|
- PENDING: 'warning',
|
|
|
- CONFIRMED: 'success',
|
|
|
- CANCELLED: 'info',
|
|
|
- COMPLETED: 'primary',
|
|
|
- CANCEL_REQUESTED: 'danger'
|
|
|
- }
|
|
|
- return map[status] || 'info'
|
|
|
+const handleReset = () => {
|
|
|
+ statusFilter.value = ''
|
|
|
+ typeFilter.value = ''
|
|
|
+ pageNum.value = 1
|
|
|
+ fetchOrders()
|
|
|
}
|
|
|
|
|
|
fetchOrders()
|