OrderDetail.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <template>
  2. <div class="order-detail admin-page" v-loading="loading">
  3. <div class="page-header">
  4. <el-button type="text" @click="$router.push('/order-manage')">&lt; 返回订单管理</el-button>
  5. <h2>订单详情</h2>
  6. </div>
  7. <template v-if="order">
  8. <el-card style="margin-bottom:20px;">
  9. <div slot="header">
  10. <span>基本信息</span>
  11. <el-tag :type="statusType(order.status)" style="margin-left:10px;">{{ statusLabel(order.status) }}</el-tag>
  12. </div>
  13. <el-descriptions :column="3" border>
  14. <el-descriptions-item label="订单编号">{{ order.orderNo }}</el-descriptions-item>
  15. <el-descriptions-item label="商品名称">{{ order.productName || '-' }}</el-descriptions-item>
  16. <el-descriptions-item label="商品类型">{{ typeLabel(order.productType) }}</el-descriptions-item>
  17. <el-descriptions-item label="数量">{{ order.quantity }}</el-descriptions-item>
  18. <el-descriptions-item label="单价">{{ formatPrice(order.unitPrice) }}</el-descriptions-item>
  19. <el-descriptions-item label="总金额">{{ formatPrice(order.totalAmount) }}</el-descriptions-item>
  20. <el-descriptions-item label="优惠金额">{{ order.discountAmount ? formatPrice(order.discountAmount) : '-' }}</el-descriptions-item>
  21. <el-descriptions-item label="支付方式">{{ payMethodLabel(order.paymentMethod) }}</el-descriptions-item>
  22. <el-descriptions-item label="支付时间">{{ order.paidAt ? formatTime(order.paidAt) : '-' }}</el-descriptions-item>
  23. <el-descriptions-item label="下单时间">{{ formatTime(order.createdAt) }}</el-descriptions-item>
  24. <el-descriptions-item label="买家备注">{{ order.remark || '-' }}</el-descriptions-item>
  25. <el-descriptions-item label="交易号">{{ order.transactionId || '-' }}</el-descriptions-item>
  26. </el-descriptions>
  27. </el-card>
  28. <el-card style="margin-bottom:20px;">
  29. <div slot="header"><span>收货信息</span></div>
  30. <el-descriptions :column="3" border>
  31. <el-descriptions-item label="配送方式">{{ order.deliveryMethod == 2 ? '自提' : '快递' }}</el-descriptions-item>
  32. <el-descriptions-item label="自提码">{{ order.pickupCode || '-' }}</el-descriptions-item>
  33. <el-descriptions-item label="收货地址">{{ order.addressSnapshot || '-' }}</el-descriptions-item>
  34. <el-descriptions-item label="物流公司">{{ order.logisticsCompany || '-' }}</el-descriptions-item>
  35. <el-descriptions-item label="物流单号">{{ order.logisticsNo || order.trackingNumber || '-' }}</el-descriptions-item>
  36. </el-descriptions>
  37. </el-card>
  38. <el-card v-if="order.refundStatus" style="margin-bottom:20px;">
  39. <div slot="header"><span>退款信息</span></div>
  40. <el-descriptions :column="3" border>
  41. <el-descriptions-item label="退款状态">{{ refundStatusLabel(order.refundStatus) }}</el-descriptions-item>
  42. <el-descriptions-item label="退款原因">{{ order.refundReason || '-' }}</el-descriptions-item>
  43. <el-descriptions-item label="退款金额">{{ order.refundAmount ? formatPrice(order.refundAmount) : '-' }}</el-descriptions-item>
  44. <el-descriptions-item label="退款时间">{{ order.refundTime ? formatTime(order.refundTime) : '-' }}</el-descriptions-item>
  45. </el-descriptions>
  46. </el-card>
  47. <div style="margin-top:20px;">
  48. <el-button type="success" @click="handleShip" v-if="order.status === 'paid'">发货</el-button>
  49. <el-button type="danger" @click="handleClose" v-if="['paid','pending_receipt','refunding'].includes(order.status)">关闭订单</el-button>
  50. <el-button type="warning" @click="showRefundDialog" v-if="order.status === 'refunding'">退款审核</el-button>
  51. </div>
  52. </template>
  53. <div v-else-if="!loading" style="text-align:center;padding:60px;color:#999;">
  54. <p>未找到订单信息</p>
  55. </div>
  56. <!-- Ship Dialog -->
  57. <el-dialog title="发货" :visible.sync="shipDialogVisible" width="450px">
  58. <el-alert title="确认发货" type="info" :closable="false" style="margin-bottom:15px;">
  59. <template slot>订单号:<strong>{{ order?.orderNo }}</strong></template>
  60. </el-alert>
  61. <el-form :model="shipForm" label-width="90px">
  62. <el-form-item label="物流公司">
  63. <el-input v-model="shipForm.expressCompany" placeholder="如:顺丰、中通" />
  64. </el-form-item>
  65. <el-form-item label="物流单号">
  66. <el-input v-model="shipForm.trackingNumber" placeholder="请输入物流单号" />
  67. </el-form-item>
  68. </el-form>
  69. <span slot="footer">
  70. <el-button @click="shipDialogVisible = false">取消</el-button>
  71. <el-button type="primary" @click="confirmShip" :loading="shipLoading">确认发货</el-button>
  72. </span>
  73. </el-dialog>
  74. <!-- Refund Dialog -->
  75. <el-dialog title="退款审核" :visible.sync="refundDialogVisible" width="450px">
  76. <el-alert title="退款申请" type="warning" :closable="false" style="margin-bottom:15px;">
  77. <template slot>
  78. 订单号:<strong>{{ order?.orderNo }}</strong><br>
  79. 退款原因:{{ order?.refundReason || '-' }}
  80. </template>
  81. </el-alert>
  82. <el-input v-model="refundRejectReason" type="textarea" placeholder="拒绝原因(选填)" :rows="3" />
  83. <span slot="footer">
  84. <el-button @click="refundDialogVisible = false">取消</el-button>
  85. <el-button type="danger" @click="handleRejectRefund" :loading="refundLoading">拒绝退款</el-button>
  86. <el-button type="success" @click="handleApproveRefundConfirm" :loading="refundLoading">同意退款</el-button>
  87. </span>
  88. </el-dialog>
  89. </div>
  90. </template>
  91. <script>
  92. import { getOrderDetail, shipOrder, closeOrder, approveRefund, rejectRefund } from '@/api/admin'
  93. export default {
  94. data() {
  95. return {
  96. order: null,
  97. loading: false,
  98. shipDialogVisible: false,
  99. shipLoading: false,
  100. shipForm: { expressCompany: '', trackingNumber: '' },
  101. refundDialogVisible: false,
  102. refundLoading: false,
  103. refundRejectReason: ''
  104. }
  105. },
  106. mounted() {
  107. this.loadOrder()
  108. },
  109. methods: {
  110. async loadOrder() {
  111. var orderNo = this.$route.params.orderNo
  112. if (!orderNo) return
  113. this.loading = true
  114. try {
  115. var res = await getOrderDetail(orderNo)
  116. this.order = res.data
  117. } catch (e) {
  118. this.$message.error('加载订单详情失败')
  119. } finally {
  120. this.loading = false
  121. }
  122. },
  123. handleShip() {
  124. this.shipForm = { expressCompany: '', trackingNumber: '' }
  125. this.shipDialogVisible = true
  126. },
  127. async confirmShip() {
  128. if (!this.shipForm.trackingNumber.trim()) {
  129. return this.$message.warning('请填写物流单号')
  130. }
  131. this.shipLoading = true
  132. try {
  133. await shipOrder(this.order.orderNo, this.shipForm.trackingNumber, this.shipForm.expressCompany)
  134. this.$message.success('发货成功')
  135. this.shipDialogVisible = false
  136. this.loadOrder()
  137. } catch (e) {
  138. this.$message.error(e.message || '发货失败')
  139. } finally {
  140. this.shipLoading = false
  141. }
  142. },
  143. async handleClose() {
  144. try {
  145. await this.$confirm('确定关闭订单 ' + this.order.orderNo + '?', '提示', { type: 'warning' })
  146. await closeOrder(this.order.orderNo)
  147. this.$message.success('订单已关闭')
  148. this.loadOrder()
  149. } catch (e) {
  150. if (e !== 'cancel') {
  151. this.$message.error(e.message || '关闭失败')
  152. }
  153. }
  154. },
  155. showRefundDialog() {
  156. this.refundRejectReason = ''
  157. this.refundDialogVisible = true
  158. },
  159. async handleApproveRefundConfirm() {
  160. this.refundLoading = true
  161. try {
  162. await this.$confirm('确认同意退款?', '提示', { type: 'warning' })
  163. await approveRefund(this.order.orderNo)
  164. this.$message.success('已同意退款')
  165. this.refundDialogVisible = false
  166. this.loadOrder()
  167. } catch (e) {
  168. if (e !== 'cancel') {
  169. this.$message.error(e.message || '操作失败')
  170. }
  171. } finally {
  172. this.refundLoading = false
  173. }
  174. },
  175. async handleRejectRefund() {
  176. this.refundLoading = true
  177. try {
  178. await this.$confirm('确认拒绝退款?', '提示', { type: 'warning' })
  179. await rejectRefund(this.order.orderNo, this.refundRejectReason)
  180. this.$message.success('已拒绝退款')
  181. this.refundDialogVisible = false
  182. this.loadOrder()
  183. } catch (e) {
  184. if (e !== 'cancel') {
  185. this.$message.error(e.message || '操作失败')
  186. }
  187. } finally {
  188. this.refundLoading = false
  189. }
  190. },
  191. statusType(status) {
  192. var map = { pending: 'warning', paid: 'primary', pending_receipt: '', completed: 'success', cancelled: 'info', refunding: 'danger', refunded: 'danger', closed: 'info' }
  193. return map[status] || 'info'
  194. },
  195. statusLabel(status) {
  196. var map = { pending: '待支付', paid: '待发货', pending_receipt: '待收货', completed: '已完成', cancelled: '已取消', refunding: '退款中', refunded: '已退款', closed: '已关闭' }
  197. return map[status] || status
  198. },
  199. payMethodLabel(method) {
  200. var map = { wechat: '微信支付', balance: '余额支付', points: '积分兑换', test: '测试支付' }
  201. return map[method] || method || '-'
  202. },
  203. typeLabel(type) {
  204. var map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
  205. return map[type] || type || '-'
  206. },
  207. refundStatusLabel(rs) {
  208. var map = { 0: '无退款', 1: '申请中', 2: '已退款', 3: '已拒绝' }
  209. return map[rs] || rs || '-'
  210. },
  211. formatPrice(cents) {
  212. if (cents == null) return '-'
  213. return '¥' + (cents / 100).toFixed(2)
  214. },
  215. formatTime(time) {
  216. if (!time) return '-'
  217. return new Date(time).toLocaleString()
  218. }
  219. }
  220. }
  221. </script>
  222. <style scoped>
  223. .order-detail { padding: 20px; }
  224. .page-header { margin-bottom: 20px; }
  225. .page-header h2 { margin: 10px 0 0; }
  226. </style>