|
|
@@ -0,0 +1,253 @@
|
|
|
+<template>
|
|
|
+ <div class="admin-order-list">
|
|
|
+ <h1>订单审核</h1>
|
|
|
+
|
|
|
+ <div class="toolbar">
|
|
|
+ <el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="fetchOrders">
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="orders" border stripe v-loading="loading">
|
|
|
+ <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">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button v-if="row.status === 'PENDING'" size="small" type="primary" @click="openReview(row)">审核</el-button>
|
|
|
+ <el-button v-if="row.status === 'CANCEL_REQUESTED'" size="small" type="warning" @click="openCancelHandle(row)">处理取消</el-button>
|
|
|
+ <el-button size="small" @click="viewDetail(row.id)">详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pageNum"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :total="total"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ @current-change="fetchOrders"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 审核对话框 -->
|
|
|
+ <el-dialog v-model="reviewVisible" title="订单审核" width="400px">
|
|
|
+ <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-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="原因">
|
|
|
+ <el-input v-model="reviewReason" type="textarea" :rows="3" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="reviewVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitReview">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 处理取消对话框 -->
|
|
|
+ <el-dialog v-model="cancelVisible" title="处理取消申请" width="400px">
|
|
|
+ <p>取消原因:{{ cancelReason }}</p>
|
|
|
+ <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-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处理意见">
|
|
|
+ <el-input v-model="cancelOpinion" type="textarea" :rows="3" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="cancelVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitCancel">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref } from 'vue'
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import type { Appointment } from '@/type'
|
|
|
+import { getAdminAppointmentController } from '@/api/admin-appointment-controller'
|
|
|
+import type { ListAllParams, ReviewDto, HandleCancelDto } from '@/api/models'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+const orders = ref<Appointment[]>([])
|
|
|
+const loading = ref(false)
|
|
|
+const statusFilter = ref('')
|
|
|
+const pageNum = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const total = ref(0)
|
|
|
+
|
|
|
+// Review dialog
|
|
|
+const reviewVisible = ref(false)
|
|
|
+const reviewAction = ref('CONFIRMED')
|
|
|
+const reviewReason = ref('')
|
|
|
+const reviewingOrder = ref<Appointment | null>(null)
|
|
|
+
|
|
|
+// Cancel dialog
|
|
|
+const cancelVisible = ref(false)
|
|
|
+const cancelAction = ref('CANCELLED')
|
|
|
+const cancelOpinion = ref('')
|
|
|
+const cancelReason = ref('')
|
|
|
+const cancelOrder = ref<Appointment | null>(null)
|
|
|
+
|
|
|
+const fetchOrders = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const api = getAdminAppointmentController()
|
|
|
+ const params: ListAllParams = {
|
|
|
+ pageNum: pageNum.value,
|
|
|
+ pageSize: pageSize.value
|
|
|
+ }
|
|
|
+ if (statusFilter.value) params.status = statusFilter.value
|
|
|
+ const res = await api.listAll(params)
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ orders.value = (res.data.data ?? []).map(a => ({
|
|
|
+ id: String(a.id ?? ''),
|
|
|
+ userId: String(a.userId ?? ''),
|
|
|
+ petId: String(a.petId ?? ''),
|
|
|
+ packageId: String(a.packageId ?? ''),
|
|
|
+ roomId: a.roomId ? String(a.roomId) : undefined,
|
|
|
+ type: (a.type as Appointment['type']) ?? 'GROOMING',
|
|
|
+ startDate: a.startDate,
|
|
|
+ endDate: a.endDate,
|
|
|
+ status: (a.status as Appointment['status']) ?? 'PENDING',
|
|
|
+ deposit: a.deposit ?? 0,
|
|
|
+ depositPaid: a.depositPaid ?? false,
|
|
|
+ totalPrice: a.totalPrice ?? 0,
|
|
|
+ cancelReason: a.cancelReason,
|
|
|
+ rejectReason: a.rejectReason,
|
|
|
+ createdAt: a.createdAt ?? ''
|
|
|
+ }))
|
|
|
+ total.value = res.data.total ?? 0
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ ElMessage.error(err.message || '加载失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const openReview = (order: Appointment) => {
|
|
|
+ reviewingOrder.value = order
|
|
|
+ reviewAction.value = 'CONFIRMED'
|
|
|
+ reviewReason.value = ''
|
|
|
+ reviewVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const submitReview = async () => {
|
|
|
+ if (!reviewingOrder.value) return
|
|
|
+ try {
|
|
|
+ const api = getAdminAppointmentController()
|
|
|
+ const dto: ReviewDto = {
|
|
|
+ action: reviewAction.value,
|
|
|
+ rejectReason: reviewAction.value === 'CANCELLED' ? reviewReason.value : undefined
|
|
|
+ }
|
|
|
+ const res = await api.review(Number(reviewingOrder.value.id), dto)
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('审核完成')
|
|
|
+ reviewVisible.value = false
|
|
|
+ await fetchOrders()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ ElMessage.error(err.message || '操作失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const openCancelHandle = (order: Appointment) => {
|
|
|
+ cancelOrder.value = order
|
|
|
+ cancelReason.value = order.cancelReason || ''
|
|
|
+ cancelAction.value = 'CANCELLED'
|
|
|
+ cancelOpinion.value = ''
|
|
|
+ cancelVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const submitCancel = async () => {
|
|
|
+ if (!cancelOrder.value) return
|
|
|
+ try {
|
|
|
+ const api = getAdminAppointmentController()
|
|
|
+ const dto: HandleCancelDto = { action: cancelAction.value }
|
|
|
+ const res = await api.handleCancel(Number(cancelOrder.value.id), dto)
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('处理完成')
|
|
|
+ cancelVisible.value = false
|
|
|
+ await fetchOrders()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.message || '操作失败')
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ ElMessage.error(err.message || '操作失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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 statusTag = (status: string) => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
+ PENDING: 'warning',
|
|
|
+ CONFIRMED: 'success',
|
|
|
+ CANCELLED: 'info',
|
|
|
+ COMPLETED: 'primary',
|
|
|
+ CANCEL_REQUESTED: 'danger'
|
|
|
+ }
|
|
|
+ return map[status] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+fetchOrders()
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.admin-order-list {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+h1 {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+</style>
|