after-sales.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <template>
  2. <view class="container">
  3. <view class="status-tabs">
  4. <view
  5. v-for="tab in statusTabs"
  6. :key="tab.value"
  7. :class="['tab', currentTab === tab.value ? 'active' : '']"
  8. @click="onTabChange(tab.value)"
  9. >{{ tab.label }}</view>
  10. </view>
  11. <scroll-view
  12. scroll-y
  13. class="list-scroll"
  14. @scrolltolower="onLoadMore"
  15. >
  16. <view v-if="loading && list.length === 0" class="loading-wrap">
  17. <text class="loading-text">加载中...</text>
  18. </view>
  19. <view v-else-if="list.length === 0" class="empty-wrap">
  20. <text class="empty-icon">📋</text>
  21. <text class="empty-text">暂无售后记录</text>
  22. </view>
  23. <view v-else>
  24. <view
  25. v-for="item in list"
  26. :key="item.id"
  27. class="card"
  28. @click="goDetail(item.id)"
  29. >
  30. <view class="card-header">
  31. <text class="order-no">{{ item.orderNo }}</text>
  32. <text :class="['status-badge', 'status-' + item.status]">{{ statusLabel(item.status) }}</text>
  33. </view>
  34. <view class="card-body">
  35. <view class="info-row">
  36. <text class="info-label">类型</text>
  37. <text class="info-value">{{ item.type === 'refund' ? '仅退款' : '退货退款' }}</text>
  38. </view>
  39. <view class="info-row">
  40. <text class="info-label">退款金额</text>
  41. <text class="info-value price">{{ formatPrice(item.refundAmount) }}</text>
  42. </view>
  43. <view class="info-row">
  44. <text class="info-label">申请时间</text>
  45. <text class="info-value">{{ formatTime(item.createdAt) }}</text>
  46. </view>
  47. </view>
  48. <text class="card-arrow">›</text>
  49. </view>
  50. </view>
  51. <view v-if="loadingMore" class="loading-more">
  52. <text class="loading-text">加载中...</text>
  53. </view>
  54. </scroll-view>
  55. </view>
  56. </template>
  57. <script>
  58. import config from '@/config.js'
  59. import { parseDate } from '@/utils/format.js'
  60. export default {
  61. data() {
  62. return {
  63. statusTabs: [
  64. { label: '全部', value: '' },
  65. { label: '待审核', value: 'pending' },
  66. { label: '已通过', value: 'approved' },
  67. { label: '已拒绝', value: 'rejected' },
  68. { label: '已完成', value: 'completed' }
  69. ],
  70. currentTab: '',
  71. list: [],
  72. page: 1,
  73. size: 20,
  74. loading: false,
  75. loadingMore: false,
  76. noMore: false
  77. }
  78. },
  79. onLoad() {
  80. this.loadList()
  81. },
  82. methods: {
  83. onTabChange(value) {
  84. this.currentTab = value
  85. this.page = 1
  86. this.list = []
  87. this.noMore = false
  88. this.loadList()
  89. },
  90. loadList() {
  91. if (this.loading) return
  92. this.loading = true
  93. var that = this
  94. uni.request({
  95. url: config.api('/api/shop/after-sales/list'),
  96. method: 'POST',
  97. data: { status: this.currentTab || undefined },
  98. header: {
  99. 'Content-Type': 'application/json',
  100. 'Authorization': 'Bearer ' + uni.getStorageSync('token')
  101. },
  102. success: function(res) {
  103. that.loading = false
  104. that.loadingMore = false
  105. if (res.data && res.data.code === 200) {
  106. var records = res.data.data || []
  107. if (that.page === 1) {
  108. that.list = records
  109. } else {
  110. that.list = that.list.concat(records)
  111. }
  112. that.noMore = records.length < that.size
  113. }
  114. },
  115. fail: function() {
  116. that.loading = false
  117. that.loadingMore = false
  118. }
  119. })
  120. },
  121. onLoadMore() {
  122. if (this.noMore || this.loadingMore) return
  123. this.page = this.page + 1
  124. this.loadingMore = true
  125. this.loadList()
  126. },
  127. goDetail(id) {
  128. uni.navigateTo({ url: '/pages/shop/after-sales-detail/after-sales-detail?id=' + id })
  129. },
  130. statusLabel(status) {
  131. var map = {
  132. pending: '待审核',
  133. approved: '已通过',
  134. rejected: '已拒绝',
  135. completed: '已完成',
  136. cancelled: '已取消'
  137. }
  138. return map[status] || status
  139. },
  140. formatPrice(amount) {
  141. if (amount === null || amount === undefined) return '0.00'
  142. return '¥' + (Number(amount) / 100).toFixed(2)
  143. },
  144. formatTime(time) {
  145. if (!time) return ''
  146. var d = parseDate(time)
  147. if (!d) return ''
  148. var year = d.getFullYear()
  149. var month = String(d.getMonth() + 1).padStart(2, '0')
  150. var day = String(d.getDate()).padStart(2, '0')
  151. return year + '-' + month + '-' + day
  152. }
  153. }
  154. }
  155. </script>
  156. <style scoped>
  157. .container {
  158. min-height: 100vh;
  159. background: #f5f5f5;
  160. }
  161. .status-tabs {
  162. display: flex;
  163. background: #fff;
  164. padding: 20rpx 0;
  165. border-bottom: 1rpx solid #eee;
  166. position: sticky;
  167. top: 0;
  168. z-index: 10;
  169. }
  170. .tab {
  171. flex: 1;
  172. text-align: center;
  173. font-size: 26rpx;
  174. color: #666;
  175. padding: 10rpx 0;
  176. }
  177. .tab.active {
  178. color: #F97316;
  179. font-weight: bold;
  180. border-bottom: 4rpx solid #F97316;
  181. }
  182. .list-scroll {
  183. height: calc(100vh - 100rpx);
  184. }
  185. .loading-wrap,
  186. .empty-wrap {
  187. display: flex;
  188. flex-direction: column;
  189. align-items: center;
  190. padding-top: 200rpx;
  191. }
  192. .empty-icon {
  193. font-size: 80rpx;
  194. margin-bottom: 20rpx;
  195. }
  196. .empty-text {
  197. font-size: 28rpx;
  198. color: #999;
  199. }
  200. .card {
  201. background: #fff;
  202. margin: 20rpx;
  203. border-radius: 16rpx;
  204. padding: 24rpx;
  205. position: relative;
  206. }
  207. .card-header {
  208. display: flex;
  209. justify-content: space-between;
  210. align-items: center;
  211. padding-bottom: 16rpx;
  212. border-bottom: 1rpx solid #f5f5f5;
  213. margin-bottom: 16rpx;
  214. }
  215. .order-no {
  216. font-size: 24rpx;
  217. color: #999;
  218. }
  219. .status-badge {
  220. font-size: 24rpx;
  221. padding: 4rpx 16rpx;
  222. border-radius: 20rpx;
  223. }
  224. .status-pending {
  225. color: #F97316;
  226. background: #fff7e6;
  227. }
  228. .status-approved {
  229. color: #52c41a;
  230. background: #f6ffed;
  231. }
  232. .status-rejected {
  233. color: #ff4d4f;
  234. background: #fff2f0;
  235. }
  236. .status-completed {
  237. color: #999;
  238. background: #f5f5f5;
  239. }
  240. .status-cancelled {
  241. color: #999;
  242. background: #f5f5f5;
  243. }
  244. .card-body {
  245. margin-bottom: 8rpx;
  246. }
  247. .info-row {
  248. display: flex;
  249. justify-content: space-between;
  250. padding: 6rpx 0;
  251. }
  252. .info-label {
  253. font-size: 26rpx;
  254. color: #999;
  255. }
  256. .info-value {
  257. font-size: 26rpx;
  258. color: #333;
  259. }
  260. .price {
  261. color: #F97316;
  262. font-weight: bold;
  263. }
  264. .card-arrow {
  265. position: absolute;
  266. right: 24rpx;
  267. top: 50%;
  268. font-size: 40rpx;
  269. color: #ccc;
  270. transform: translateY(-50%);
  271. }
  272. .loading-more {
  273. text-align: center;
  274. padding: 30rpx;
  275. }
  276. .loading-text {
  277. font-size: 24rpx;
  278. color: #999;
  279. }
  280. </style>