AdminOrderDetailView.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <template>
  2. <div class="admin-order-detail">
  3. <el-button text @click="router.back()">← 返回</el-button>
  4. <el-card v-if="order" class="detail-card">
  5. <h2>订单详情</h2>
  6. <el-descriptions :column="2" border>
  7. <el-descriptions-item label="订单编号">{{ order.id }}</el-descriptions-item>
  8. <el-descriptions-item label="状态">
  9. <el-tag :type="statusTag(order.status)">{{ statusLabel(order.status) }}</el-tag>
  10. </el-descriptions-item>
  11. <el-descriptions-item label="用户">{{ order.userId }}</el-descriptions-item>
  12. <el-descriptions-item label="服务类型">{{ order.type }}</el-descriptions-item>
  13. <el-descriptions-item label="总价">¥{{ order.totalPrice }}</el-descriptions-item>
  14. <el-descriptions-item label="定金">¥{{ order.deposit }} {{ order.depositPaid ? '(已支付)' : '(未支付)' }}</el-descriptions-item>
  15. <el-descriptions-item label="创建时间">{{ order.createdAt }}</el-descriptions-item>
  16. </el-descriptions>
  17. <div class="actions">
  18. <el-button v-if="order.status === 'PENDING'" type="primary" @click="openReview">审核</el-button>
  19. <el-button v-if="order.status === 'CANCEL_REQUESTED'" type="warning" @click="openCancelHandle">处理取消</el-button>
  20. </div>
  21. </el-card>
  22. <el-dialog v-model="reviewVisible" title="订单审核" width="400px">
  23. <el-form>
  24. <el-form-item label="审核结果">
  25. <el-radio-group v-model="reviewAction">
  26. <el-radio value="CONFIRMED">通过</el-radio>
  27. <el-radio value="CANCELLED">拒绝</el-radio>
  28. </el-radio-group>
  29. </el-form-item>
  30. <el-form-item label="原因">
  31. <el-input v-model="reviewReason" type="textarea" :rows="3" />
  32. </el-form-item>
  33. </el-form>
  34. <template #footer>
  35. <el-button @click="reviewVisible = false">取消</el-button>
  36. <el-button type="primary" @click="submitReview">确认</el-button>
  37. </template>
  38. </el-dialog>
  39. <el-dialog v-model="cancelVisible" title="处理取消申请" width="400px">
  40. <p>取消原因:{{ order?.cancelReason }}</p>
  41. <el-form>
  42. <el-form-item label="处理结果">
  43. <el-radio-group v-model="cancelAction">
  44. <el-radio value="CANCELLED">同意取消</el-radio>
  45. <el-radio value="CONFIRMED">拒绝取消</el-radio>
  46. </el-radio-group>
  47. </el-form-item>
  48. </el-form>
  49. <template #footer>
  50. <el-button @click="cancelVisible = false">取消</el-button>
  51. <el-button type="primary" @click="submitCancel">确认</el-button>
  52. </template>
  53. </el-dialog>
  54. </div>
  55. </template>
  56. <script setup lang="ts">
  57. import { ref, onMounted } from 'vue'
  58. import { useRoute, useRouter } from 'vue-router'
  59. import { ElMessage } from 'element-plus'
  60. import type { Appointment } from '@/type'
  61. import { getAppointmentController } from '@/api/appointment-controller'
  62. import { getAdminAppointmentController } from '@/api/admin-appointment-controller'
  63. import type { ReviewDto, HandleCancelDto } from '@/api/models'
  64. const route = useRoute()
  65. const router = useRouter()
  66. const order = ref<Appointment | null>(null)
  67. const loading = ref(false)
  68. const reviewVisible = ref(false)
  69. const reviewAction = ref('CONFIRMED')
  70. const reviewReason = ref('')
  71. const cancelVisible = ref(false)
  72. const cancelAction = ref('CANCELLED')
  73. const loadOrder = async () => {
  74. loading.value = true
  75. try {
  76. const api = getAppointmentController()
  77. const res = await api.getById2(Number(route.params.id))
  78. if (res.code === 200 && res.data) {
  79. const a = res.data
  80. order.value = {
  81. id: String(a.id ?? ''),
  82. userId: String(a.userId ?? ''),
  83. petId: String(a.petId ?? ''),
  84. packageId: String(a.packageId ?? ''),
  85. roomId: a.roomId ? String(a.roomId) : undefined,
  86. type: (a.type as Appointment['type']) ?? 'GROOMING',
  87. startDate: a.startDate,
  88. endDate: a.endDate,
  89. status: (a.status as Appointment['status']) ?? 'PENDING',
  90. deposit: a.deposit ?? 0,
  91. depositPaid: a.depositPaid ?? false,
  92. totalPrice: a.totalPrice ?? 0,
  93. cancelReason: a.cancelReason,
  94. rejectReason: a.rejectReason,
  95. createdAt: a.createdAt ?? ''
  96. }
  97. }
  98. } catch (err: any) {
  99. ElMessage.error(err.message || '加载失败')
  100. } finally {
  101. loading.value = false
  102. }
  103. }
  104. const openReview = () => {
  105. reviewAction.value = 'CONFIRMED'
  106. reviewReason.value = ''
  107. reviewVisible.value = true
  108. }
  109. const submitReview = async () => {
  110. if (!order.value) return
  111. try {
  112. const api = getAdminAppointmentController()
  113. const dto: ReviewDto = {
  114. action: reviewAction.value,
  115. rejectReason: reviewAction.value === 'CANCELLED' ? reviewReason.value : undefined
  116. }
  117. const res = await api.review(Number(order.value.id), dto)
  118. if (res.code === 200) {
  119. ElMessage.success('审核完成')
  120. reviewVisible.value = false
  121. await loadOrder()
  122. } else {
  123. ElMessage.error(res.message || '操作失败')
  124. }
  125. } catch (err: any) {
  126. ElMessage.error(err.message || '操作失败')
  127. }
  128. }
  129. const openCancelHandle = () => {
  130. cancelAction.value = 'CANCELLED'
  131. cancelVisible.value = true
  132. }
  133. const submitCancel = async () => {
  134. if (!order.value) return
  135. try {
  136. const api = getAdminAppointmentController()
  137. const dto: HandleCancelDto = { action: cancelAction.value }
  138. const res = await api.handleCancel(Number(order.value.id), dto)
  139. if (res.code === 200) {
  140. ElMessage.success('处理完成')
  141. cancelVisible.value = false
  142. await loadOrder()
  143. } else {
  144. ElMessage.error(res.message || '操作失败')
  145. }
  146. } catch (err: any) {
  147. ElMessage.error(err.message || '操作失败')
  148. }
  149. }
  150. const statusLabel = (status: string) => {
  151. const map: Record<string, string> = {
  152. PENDING: '待审核', CONFIRMED: '已确认', CANCELLED: '已取消',
  153. COMPLETED: '已完成', CANCEL_REQUESTED: '取消申请'
  154. }
  155. return map[status] || status
  156. }
  157. const statusTag = (status: string) => {
  158. const map: Record<string, string> = {
  159. PENDING: 'warning', CONFIRMED: 'success', CANCELLED: 'info',
  160. COMPLETED: 'primary', CANCEL_REQUESTED: 'danger'
  161. }
  162. return map[status] || 'info'
  163. }
  164. onMounted(loadOrder)
  165. </script>
  166. <style scoped>
  167. .admin-order-detail {
  168. max-width: 800px;
  169. padding: 20px;
  170. }
  171. .detail-card {
  172. margin-top: 16px;
  173. }
  174. .actions {
  175. margin-top: 24px;
  176. display: flex;
  177. gap: 12px;
  178. }
  179. </style>