order_item.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <template>
  2. <view class="order-item" @click="handleClick">
  3. <!-- 订单头部 -->
  4. <view class="order-header">
  5. <view class="order-info">
  6. <text class="order-number">{{ t('订单号') }}:{{ item.orderNo }}</text>
  7. <text class="order-time">{{ formatTime(item.createTime) }}</text>
  8. </view>
  9. <view class="order-status" :class="getStatusClass(item.status)">
  10. {{ getStatusText(item.status) }}
  11. </view>
  12. </view>
  13. <!-- 商品信息 -->
  14. <view class="product-section">
  15. <view class="product-list">
  16. <view
  17. v-for="(product, index) in item.goods"
  18. :key="index"
  19. class="product-item"
  20. >
  21. <view class="product-image">
  22. <image :src="product.image" mode="aspectFill" />
  23. </view>
  24. <view class="product-info">
  25. <view class="product-name">{{ product.name }}</view>
  26. <view class="product-spec" v-if="product.spec">{{ product.spec }}</view>
  27. <view class="product-price">
  28. <text class="price">{{ formatCurrency(product.price) }}</text>
  29. <text class="quantity">x{{ product.quantity }}</text>
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. </view>
  35. <!-- 订单金额 -->
  36. <view class="order-amount">
  37. <view class="amount-info">
  38. <text class="total-label">{{ t('商品数量') }}:{{ item.totalQuantity }}</text>
  39. <text class="total-amount">{{ t('实付') }}:{{ formatCurrency(item.totalAmount) }}</text>
  40. </view>
  41. <!-- 待付款运费 -->
  42. <view class="shipping-fee" v-if="item.status == 300 && item.money">
  43. <text class="fee-label">{{ t('运费') }}:</text>
  44. <text class="fee-amount">{{ formatCurrency(item.money) }}</text>
  45. </view>
  46. </view>
  47. <!-- 操作按钮 -->
  48. <view class="order-actions" v-if="getActionButtons(item.status).length > 0">
  49. <view
  50. v-for="action in getActionButtons(item.status)"
  51. :key="action.type"
  52. class="action-btn"
  53. :class="action.class"
  54. @click.stop="handleAction(action.type)"
  55. >
  56. {{ action.text }}
  57. </view>
  58. </view>
  59. </view>
  60. </template>
  61. <script setup>
  62. import { defineProps, defineEmits } from "vue";
  63. import { formatCurrency } from "@/utils";
  64. import { t } from "@/locale";
  65. const props = defineProps({
  66. item: {
  67. type: Object,
  68. required: true,
  69. },
  70. });
  71. const emit = defineEmits(["click", "action"]);
  72. const handleClick = () => {
  73. emit("click", props.item);
  74. };
  75. const handleAction = (actionType) => {
  76. emit("action", props.item, actionType);
  77. };
  78. // 格式化时间
  79. const formatTime = (timestamp) => {
  80. if (!timestamp) return '';
  81. const date = new Date(timestamp);
  82. const year = date.getFullYear();
  83. const month = String(date.getMonth() + 1).padStart(2, '0');
  84. const day = String(date.getDate()).padStart(2, '0');
  85. const hours = String(date.getHours()).padStart(2, '0');
  86. const minutes = String(date.getMinutes()).padStart(2, '0');
  87. return `${year}-${month}-${day} ${hours}:${minutes}`;
  88. };
  89. // 获取状态文本
  90. const getStatusText = (status) => {
  91. const statusMap = {
  92. 0: '待付款',
  93. 100: '待发货',
  94. 200: '已发货',
  95. 300: '待发货',
  96. 400: '已完成',
  97. 500: '已取消',
  98. 600: '已退款'
  99. };
  100. return statusMap[status] || '未知状态';
  101. };
  102. // 获取状态样式类
  103. const getStatusClass = (status) => {
  104. const classMap = {
  105. 0: 'status-pending',
  106. 100: 'status-shipping',
  107. 200: 'status-shipped',
  108. 300: 'status-shipping',
  109. 400: 'status-completed',
  110. 500: 'status-cancelled',
  111. 600: 'status-refunded'
  112. };
  113. return classMap[status] || 'status-default';
  114. };
  115. // 获取操作按钮
  116. const getActionButtons = (status) => {
  117. const buttonMap = {
  118. 0: [], // 待付款 - 买家操作
  119. 100: [
  120. { type: 'ship', text: '发货', class: 'btn-primary' }
  121. ],
  122. 200: [], // 已发货 - 买家操作
  123. 300: [
  124. { type: 'ship', text: '发货', class: 'btn-primary' }
  125. ],
  126. 400: [], // 已完成
  127. 500: [], // 已取消
  128. 600: [] // 已退款
  129. };
  130. return buttonMap[status] || [];
  131. };
  132. </script>
  133. <style lang="less" scoped>
  134. @import url("@/style.less");
  135. .order-item {
  136. background: var(--light);
  137. border-radius: 16rpx;
  138. padding: 30rpx;
  139. margin-bottom: 20rpx;
  140. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  141. .order-header {
  142. display: flex;
  143. justify-content: space-between;
  144. align-items: center;
  145. margin-bottom: 24rpx;
  146. padding-bottom: 20rpx;
  147. border-bottom: 1rpx solid var(--border);
  148. .order-info {
  149. display: flex;
  150. flex-direction: column;
  151. gap: 8rpx;
  152. .order-number {
  153. font-size: 28rpx;
  154. font-weight: bold;
  155. color: var(--black);
  156. }
  157. .order-time {
  158. font-size: 24rpx;
  159. color: var(--text-01);
  160. }
  161. }
  162. .order-status {
  163. font-size: 24rpx;
  164. font-weight: bold;
  165. padding: 8rpx 16rpx;
  166. border-radius: 20rpx;
  167. &.status-pending {
  168. color: var(--primary);
  169. background: rgba(255, 107, 53, 0.1);
  170. }
  171. &.status-shipping {
  172. color: var(--primary);
  173. background: rgba(255, 107, 53, 0.1);
  174. }
  175. &.status-shipped {
  176. color: var(--success);
  177. background: rgba(76, 175, 80, 0.1);
  178. }
  179. &.status-completed {
  180. color: var(--success);
  181. background: rgba(76, 175, 80, 0.1);
  182. }
  183. &.status-cancelled {
  184. color: var(--text-01);
  185. background: rgba(158, 158, 158, 0.1);
  186. }
  187. &.status-refunded {
  188. color: var(--warning);
  189. background: rgba(255, 193, 7, 0.1);
  190. }
  191. }
  192. }
  193. .product-section {
  194. margin-bottom: 24rpx;
  195. .product-list {
  196. display: flex;
  197. flex-direction: column;
  198. gap: 20rpx;
  199. .product-item {
  200. display: flex;
  201. gap: 20rpx;
  202. .product-image {
  203. width: 120rpx;
  204. height: 120rpx;
  205. border-radius: 12rpx;
  206. overflow: hidden;
  207. background: var(--bg);
  208. image {
  209. width: 100%;
  210. height: 100%;
  211. }
  212. }
  213. .product-info {
  214. flex: 1;
  215. display: flex;
  216. flex-direction: column;
  217. justify-content: space-between;
  218. .product-name {
  219. font-size: 28rpx;
  220. color: var(--black);
  221. line-height: 1.4;
  222. margin-bottom: 8rpx;
  223. overflow: hidden;
  224. text-overflow: ellipsis;
  225. display: -webkit-box;
  226. -webkit-line-clamp: 2;
  227. line-clamp: 2;
  228. -webkit-box-orient: vertical;
  229. }
  230. .product-spec {
  231. font-size: 24rpx;
  232. color: var(--text-01);
  233. margin-bottom: 8rpx;
  234. }
  235. .product-price {
  236. display: flex;
  237. justify-content: space-between;
  238. align-items: center;
  239. .price {
  240. font-size: 28rpx;
  241. font-weight: bold;
  242. color: var(--primary);
  243. }
  244. .quantity {
  245. font-size: 24rpx;
  246. color: var(--text-01);
  247. }
  248. }
  249. }
  250. }
  251. }
  252. }
  253. .order-amount {
  254. margin-bottom: 24rpx;
  255. padding-top: 20rpx;
  256. border-top: 1rpx solid var(--border);
  257. .amount-info {
  258. display: flex;
  259. justify-content: space-between;
  260. align-items: center;
  261. margin-bottom: 12rpx;
  262. .total-label {
  263. font-size: 24rpx;
  264. color: var(--text-01);
  265. }
  266. .total-amount {
  267. font-size: 32rpx;
  268. font-weight: bold;
  269. color: var(--black);
  270. }
  271. }
  272. .shipping-fee {
  273. display: flex;
  274. justify-content: space-between;
  275. align-items: center;
  276. padding: 8rpx 0;
  277. background: rgba(255, 193, 7, 0.1);
  278. border-radius: 8rpx;
  279. padding: 12rpx 16rpx;
  280. .fee-label {
  281. font-size: 24rpx;
  282. color: var(--warning);
  283. }
  284. .fee-amount {
  285. font-size: 26rpx;
  286. font-weight: bold;
  287. color: var(--warning);
  288. }
  289. }
  290. }
  291. .order-actions {
  292. display: flex;
  293. justify-content: flex-end;
  294. gap: 20rpx;
  295. padding-top: 20rpx;
  296. border-top: 1rpx solid var(--border);
  297. .action-btn {
  298. padding: 16rpx 32rpx;
  299. border-radius: 40rpx;
  300. font-size: 24rpx;
  301. font-weight: bold;
  302. text-align: center;
  303. min-width: 120rpx;
  304. &.btn-primary {
  305. background: var(--primary);
  306. color: var(--light);
  307. }
  308. &.btn-secondary {
  309. background: var(--light);
  310. color: var(--black);
  311. border: 1rpx solid var(--border);
  312. }
  313. &.btn-danger {
  314. background: var(--danger);
  315. color: var(--light);
  316. }
  317. }
  318. }
  319. }
  320. </style>