OrderDetail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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="收货人">{{ addressInfo.receiverName || '-' }}</el-descriptions-item>
  34. <el-descriptions-item label="联系电话">{{ addressInfo.phone || '-' }}</el-descriptions-item>
  35. <el-descriptions-item label="收货地址">{{ addressInfo.fullAddress }}</el-descriptions-item>
  36. <el-descriptions-item label="物流公司">{{ order.logisticsCompany || '-' }}</el-descriptions-item>
  37. <el-descriptions-item label="物流单号">{{ order.logisticsNo || order.trackingNumber || '-' }}</el-descriptions-item>
  38. </el-descriptions>
  39. </el-card>
  40. <el-card v-if="order.refundStatus" style="margin-bottom:20px;">
  41. <div slot="header"><span>退款信息</span></div>
  42. <el-descriptions :column="3" border>
  43. <el-descriptions-item label="退款状态">{{ refundStatusLabel(order.refundStatus) }}</el-descriptions-item>
  44. <el-descriptions-item label="退款原因">{{ order.refundReason || '-' }}</el-descriptions-item>
  45. <el-descriptions-item label="退款金额">{{ order.refundAmount ? formatPrice(order.refundAmount) : '-' }}</el-descriptions-item>
  46. <el-descriptions-item label="退款时间">{{ order.refundTime ? formatTime(order.refundTime) : '-' }}</el-descriptions-item>
  47. </el-descriptions>
  48. </el-card>
  49. <div style="margin-top:20px;">
  50. <el-button type="success" @click="handleShip" v-if="order.status === 'paid'">发货</el-button>
  51. <el-button type="danger" @click="handleClose" v-if="['paid','pending_receipt','refunding'].includes(order.status)">关闭订单</el-button>
  52. <el-button type="warning" @click="showRefundDialog" v-if="order.status === 'refunding'">退款审核</el-button>
  53. </div>
  54. </template>
  55. <div v-else-if="!loading" style="text-align:center;padding:60px;color:#999;">
  56. <p>未找到订单信息</p>
  57. </div>
  58. <!-- Ship Dialog -->
  59. <el-dialog title="发货" :visible.sync="shipDialogVisible" width="450px">
  60. <el-alert title="确认发货" type="info" :closable="false" style="margin-bottom:15px;">
  61. <template slot>订单号:<strong>{{ order?.orderNo }}</strong></template>
  62. </el-alert>
  63. <el-form :model="shipForm" label-width="90px">
  64. <el-form-item label="物流公司">
  65. <el-input v-model="shipForm.expressCompany" placeholder="如:顺丰、中通" />
  66. </el-form-item>
  67. <el-form-item label="物流单号">
  68. <el-input v-model="shipForm.trackingNumber" placeholder="请输入物流单号" />
  69. </el-form-item>
  70. </el-form>
  71. <span slot="footer">
  72. <el-button @click="shipDialogVisible = false">取消</el-button>
  73. <el-button type="primary" @click="confirmShip" :loading="shipLoading">确认发货</el-button>
  74. </span>
  75. </el-dialog>
  76. <!-- Refund Dialog -->
  77. <el-dialog title="退款审核" :visible.sync="refundDialogVisible" width="450px">
  78. <el-alert title="退款申请" type="warning" :closable="false" style="margin-bottom:15px;">
  79. <template slot>
  80. 订单号:<strong>{{ order?.orderNo }}</strong><br>
  81. 退款原因:{{ order?.refundReason || '-' }}
  82. </template>
  83. </el-alert>
  84. <el-input v-model="refundRejectReason" type="textarea" placeholder="拒绝原因(选填)" :rows="3" />
  85. <span slot="footer">
  86. <el-button @click="refundDialogVisible = false">取消</el-button>
  87. <el-button type="danger" @click="handleRejectRefund" :loading="refundLoading">拒绝退款</el-button>
  88. <el-button type="success" @click="handleApproveRefundConfirm" :loading="refundLoading">同意退款</el-button>
  89. </span>
  90. </el-dialog>
  91. </div>
  92. </template>
  93. <script>
  94. import { getOrderDetail, shipOrder, closeOrder, approveRefund, rejectRefund } from '@/api/admin'
  95. export default {
  96. data() {
  97. return {
  98. order: null,
  99. loading: false,
  100. shipDialogVisible: false,
  101. shipLoading: false,
  102. shipForm: { expressCompany: '', trackingNumber: '' },
  103. refundDialogVisible: false,
  104. refundLoading: false,
  105. refundRejectReason: ''
  106. }
  107. },
  108. computed: {
  109. addressInfo() {
  110. var s = this.order && this.order.addressSnapshot
  111. if (!s) return { receiverName: '', phone: '', fullAddress: '-' }
  112. try {
  113. var a = JSON.parse(s)
  114. if (a.receiverName || a.phone || a.province || a.street) {
  115. var full = String((a.province || '') + (a.city || '') + (a.district || '') + (a.street || '')).trim()
  116. return {
  117. receiverName: a.receiverName || '',
  118. phone: a.phone || '',
  119. fullAddress: full || '-'
  120. }
  121. }
  122. return { receiverName: '', phone: '', fullAddress: s }
  123. } catch (e) {
  124. return { receiverName: '', phone: '', fullAddress: s }
  125. }
  126. }
  127. },
  128. mounted() {
  129. this.loadOrder()
  130. },
  131. methods: {
  132. async loadOrder() {
  133. var orderNo = this.$route.params.orderNo
  134. if (!orderNo) return
  135. this.loading = true
  136. try {
  137. var res = await getOrderDetail(orderNo)
  138. this.order = res.data
  139. } catch (e) {
  140. this.$message.error('加载订单详情失败')
  141. } finally {
  142. this.loading = false
  143. }
  144. },
  145. handleShip() {
  146. this.shipForm = { expressCompany: '', trackingNumber: '' }
  147. this.shipDialogVisible = true
  148. },
  149. async confirmShip() {
  150. if (!this.shipForm.trackingNumber.trim()) {
  151. return this.$message.warning('请填写物流单号')
  152. }
  153. this.shipLoading = true
  154. try {
  155. await shipOrder(this.order.orderNo, this.shipForm.trackingNumber, this.shipForm.expressCompany)
  156. this.$message.success('发货成功')
  157. this.shipDialogVisible = false
  158. this.loadOrder()
  159. } catch (e) {
  160. this.$message.error(e.message || '发货失败')
  161. } finally {
  162. this.shipLoading = false
  163. }
  164. },
  165. async handleClose() {
  166. try {
  167. await this.$confirm('确定关闭订单 ' + this.order.orderNo + '?', '提示', { type: 'warning' })
  168. await closeOrder(this.order.orderNo)
  169. this.$message.success('订单已关闭')
  170. this.loadOrder()
  171. } catch (e) {
  172. if (e !== 'cancel') {
  173. this.$message.error(e.message || '关闭失败')
  174. }
  175. }
  176. },
  177. showRefundDialog() {
  178. this.refundRejectReason = ''
  179. this.refundDialogVisible = true
  180. },
  181. async handleApproveRefundConfirm() {
  182. this.refundLoading = true
  183. try {
  184. await this.$confirm('确认同意退款?', '提示', { type: 'warning' })
  185. await approveRefund(this.order.orderNo)
  186. this.$message.success('已同意退款')
  187. this.refundDialogVisible = false
  188. this.loadOrder()
  189. } catch (e) {
  190. if (e !== 'cancel') {
  191. this.$message.error(e.message || '操作失败')
  192. }
  193. } finally {
  194. this.refundLoading = false
  195. }
  196. },
  197. async handleRejectRefund() {
  198. this.refundLoading = true
  199. try {
  200. await this.$confirm('确认拒绝退款?', '提示', { type: 'warning' })
  201. await rejectRefund(this.order.orderNo, this.refundRejectReason)
  202. this.$message.success('已拒绝退款')
  203. this.refundDialogVisible = false
  204. this.loadOrder()
  205. } catch (e) {
  206. if (e !== 'cancel') {
  207. this.$message.error(e.message || '操作失败')
  208. }
  209. } finally {
  210. this.refundLoading = false
  211. }
  212. },
  213. statusType(status) {
  214. var map = { pending: 'warning', paid: 'primary', pending_receipt: '', completed: 'success', cancelled: 'info', refunding: 'danger', refunded: 'danger', closed: 'info' }
  215. return map[status] || 'info'
  216. },
  217. statusLabel(status) {
  218. var map = { pending: '待支付', paid: '待发货', pending_receipt: '待收货', completed: '已完成', cancelled: '已取消', refunding: '退款中', refunded: '已退款', closed: '已关闭' }
  219. return map[status] || status
  220. },
  221. payMethodLabel(method) {
  222. var map = { wechat: '微信支付', balance: '余额支付', points: '积分兑换', test: '测试支付' }
  223. return map[method] || method || '-'
  224. },
  225. typeLabel(type) {
  226. var map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
  227. return map[type] || type || '-'
  228. },
  229. refundStatusLabel(rs) {
  230. var map = { 0: '无退款', 1: '申请中', 2: '已退款', 3: '已拒绝' }
  231. return map[rs] || rs || '-'
  232. },
  233. formatPrice(cents) {
  234. if (cents == null) return '-'
  235. return '¥' + (cents / 100).toFixed(2)
  236. },
  237. }
  238. }
  239. </script>
  240. <style scoped>
  241. .order-detail { padding: 20px; }
  242. .page-header { margin-bottom: 20px; }
  243. .page-header h2 { margin: 10px 0 0; }
  244. </style>