巴青农资商城

orderDisplay.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. import { resolveFileUrl } from '@/utils/image'
  2. import { formatPrice } from '@/utils/format'
  3. import { parseCartSpecText } from '@/utils/cartSpec'
  4. import {
  5. ORDER_ACTION,
  6. ORDER_ACTION_LABEL,
  7. REVIEW_ITEM_STATUS,
  8. AFTERSALE_APPLY_TYPE_OPTIONS
  9. } from '@/constants/order'
  10. /** 订单级不再展示的评价按钮(改在商品行) */
  11. const ORDER_LEVEL_REVIEW_CODES = [ORDER_ACTION.REVIEW, ORDER_ACTION.VIEW_REVIEW]
  12. const GOODS_PLACEHOLDER = '/static/logo.png'
  13. const SHOP_PLACEHOLDER = '/static/logo.png'
  14. /** 后端凭证图可能是逗号拼接字符串,统一转成数组 */
  15. function normalizePicList(raw) {
  16. if (!raw) return []
  17. if (Array.isArray(raw)) return raw.filter(Boolean)
  18. if (typeof raw === 'string') {
  19. return raw.split(',').map((s) => s.trim()).filter(Boolean)
  20. }
  21. return []
  22. }
  23. function mapAftersaleApplyTypeText(applyType) {
  24. const hit = AFTERSALE_APPLY_TYPE_OPTIONS.find((opt) => opt.value === applyType)
  25. return hit ? hit.label : applyType || ''
  26. }
  27. function mapAftersaleStatusText(status) {
  28. if (status === '2') return '售后完结'
  29. if (status === '1') return '商家处理中'
  30. return status || ''
  31. }
  32. function mapAftersaleProgressText(stage) {
  33. if (stage === 'FINISHED') return '商家处理 → 售后完结(当前:售后完结)'
  34. return '商家处理 → 售后完结(当前:商家处理)'
  35. }
  36. function mapFirstItem(item) {
  37. if (!item) return null
  38. const specText = (item.goodsSpec || item.specText || '').trim() || '默认'
  39. return {
  40. orderItemId: item.itemId || item.orderItemId,
  41. goodsId: item.goodsId,
  42. goodsName: item.goodsName || '',
  43. displayPic: resolveFileUrl(item.goodsImage || item.mainPic) || GOODS_PLACEHOLDER,
  44. specText,
  45. specList: parseCartSpecText(specText),
  46. quantity: Number(item.quantity) || 1,
  47. unitPrice: item.unitPrice,
  48. priceText: formatPrice(item.unitPrice),
  49. reviewStatus: item.reviewStatus,
  50. reviewId: item.reviewId
  51. }
  52. }
  53. function mapOrderItemRow(row) {
  54. if (!row) return null
  55. const specText = (row.goodsSpec || row.specText || '').trim() || '默认'
  56. return {
  57. orderItemId: row.orderItemId || row.itemId,
  58. goodsId: row.goodsId,
  59. goodsName: row.goodsName || '',
  60. displayPic: resolveFileUrl(row.goodsImage || row.mainPic) || GOODS_PLACEHOLDER,
  61. specText,
  62. specList: parseCartSpecText(specText),
  63. serviceDesc: row.serviceDesc || '',
  64. quantity: Number(row.quantity) || 1,
  65. unitPrice: row.unitPrice || row.salePrice,
  66. priceText: formatPrice(row.unitPrice || row.salePrice),
  67. lineAmount: row.lineAmount || row.subtotal,
  68. lineAmountText: formatPrice(row.lineAmount || row.subtotal),
  69. buyerRemark: row.buyerRemark || '',
  70. reviewStatus: row.reviewStatus,
  71. reviewId: row.reviewId
  72. }
  73. }
  74. function mapActions(actions) {
  75. return (actions || [])
  76. .filter((code) => !ORDER_LEVEL_REVIEW_CODES.includes(code))
  77. .map((code) => ({
  78. code,
  79. label: ORDER_ACTION_LABEL[code] || code
  80. }))
  81. .filter((item) => item.label)
  82. }
  83. /** 交易成功商品行:待评价→评价,已评价→查看评价 */
  84. export function getItemReviewAction(item) {
  85. if (!item || !item.reviewStatus) return null
  86. if (item.reviewStatus === REVIEW_ITEM_STATUS.PENDING) {
  87. return {
  88. code: ORDER_ACTION.REVIEW,
  89. label: ORDER_ACTION_LABEL[ORDER_ACTION.REVIEW]
  90. }
  91. }
  92. if (item.reviewStatus === REVIEW_ITEM_STATUS.DONE) {
  93. return {
  94. code: ORDER_ACTION.VIEW_REVIEW,
  95. label: ORDER_ACTION_LABEL[ORDER_ACTION.VIEW_REVIEW]
  96. }
  97. }
  98. return null
  99. }
  100. /** 评价列表「待评价」:订单行展平为待评价商品行 */
  101. export function flattenPendingReviewItems(rows) {
  102. const result = []
  103. for (const row of rows || []) {
  104. const card = mapOrderListRow(row)
  105. if (!card) continue
  106. for (const item of card.items || []) {
  107. if (item.reviewStatus !== REVIEW_ITEM_STATUS.PENDING) continue
  108. result.push({
  109. key: `pending-${card.orderId}-${item.orderItemId}`,
  110. orderId: card.orderId,
  111. item
  112. })
  113. }
  114. }
  115. return result
  116. }
  117. /** 评价列表「已评价」行 → 紧凑卡片模型 */
  118. export function mapReviewDoneRow(row) {
  119. if (!row) return null
  120. const item = mapFirstItem(row.firstItem)
  121. const content = (row.content || '').trim()
  122. return {
  123. key: `done-${row.reviewId || row.orderId}-${item?.orderItemId || 0}`,
  124. reviewId: row.reviewId,
  125. orderId: row.orderId,
  126. orderItemId: item?.orderItemId,
  127. score: Number(row.score) || 0,
  128. content,
  129. contentBrief: trimText(content, 48, '此用户未填写评价内容'),
  130. reviewTime: row.reviewTime || row.createTime || '',
  131. item
  132. }
  133. }
  134. function trimText(text, maxLen, emptyFallback = '') {
  135. const s = (text || '').trim()
  136. if (!s) return emptyFallback
  137. if (s.length <= maxLen) return s
  138. return `${s.slice(0, maxLen)}…`
  139. }
  140. /** 列表行 VO → 卡片模型 */
  141. export function mapOrderListRow(row) {
  142. if (!row) return null
  143. const rawItems = Array.isArray(row.items) && row.items.length
  144. ? row.items
  145. : row.firstItem
  146. ? [row.firstItem]
  147. : []
  148. const items = rawItems.map(mapFirstItem).filter(Boolean)
  149. const firstItem = items[0] || mapFirstItem(row.firstItem)
  150. return {
  151. orderId: row.orderId,
  152. orderNo: row.orderNo || '',
  153. orderStatus: row.orderStatus,
  154. statusText: row.orderStatusText || '',
  155. shopId: row.shopId,
  156. shopName: row.shopName || '',
  157. shopAvatar: resolveFileUrl(row.shopAvatar) || SHOP_PLACEHOLDER,
  158. payAmount: row.payAmount,
  159. payAmountText: formatPrice(row.payAmount),
  160. goodsAmount: row.goodsAmount,
  161. freightAmount: row.freightAmount,
  162. createTime: row.createTime || '',
  163. itemCount: Number(row.itemCount) || items.length,
  164. items,
  165. firstItem,
  166. payRemainSeconds: row.payRemainSeconds,
  167. reviewStatus: row.reviewStatus,
  168. actions: mapActions(row.actions)
  169. }
  170. }
  171. /** 详情 VO → 页面模型 */
  172. export function mapOrderDetail(data) {
  173. if (!data) return null
  174. const items = (data.items || []).map(mapOrderItemRow).filter(Boolean)
  175. const latestTrace = data.latestTrace || null
  176. return {
  177. orderId: data.orderId,
  178. orderNo: data.orderNo || '',
  179. orderStatus: data.orderStatus,
  180. statusText: data.orderStatusText || '',
  181. payStatus: data.payStatus,
  182. payType: data.payType,
  183. payTypeText: data.payTypeText || '',
  184. shopId: data.shopId,
  185. shopName: data.shopName || '',
  186. shopAvatar: resolveFileUrl(data.shopAvatar) || SHOP_PLACEHOLDER,
  187. consigneeName: data.consigneeName || '',
  188. consigneeMobile: data.consigneeMobile || '',
  189. consigneeAddress: data.consigneeAddress || '',
  190. goodsAmount: data.goodsAmount,
  191. goodsAmountText: formatPrice(data.goodsAmount),
  192. freightAmount: data.freightAmount,
  193. freightAmountText: formatPrice(data.freightAmount),
  194. freightDesc: data.freightDesc || '',
  195. payAmount: data.payAmount,
  196. payAmountText: formatPrice(data.payAmount),
  197. createTime: data.createTime || '',
  198. payTime: data.payTime || '',
  199. payExpireTime: data.payExpireTime || '',
  200. payRemainSeconds: data.payRemainSeconds,
  201. finishTime: data.finishTime || '',
  202. shipTime: data.shipTime || '',
  203. closeType: data.closeType,
  204. closeTypeText: data.closeTypeText || '',
  205. closeReason: data.closeReason || '',
  206. deliveryType: data.deliveryType,
  207. logisticsCompany: data.logisticsCompany || '',
  208. trackingNo: data.trackingNo || '',
  209. vehicleNo: data.vehicleNo || '',
  210. courierName: data.courierName || '',
  211. courierMobile: data.courierMobile || '',
  212. latestTrace: latestTrace
  213. ? {
  214. traceType: latestTrace.traceType,
  215. traceTime: latestTrace.traceTime || '',
  216. content: latestTrace.content || ''
  217. }
  218. : null,
  219. items,
  220. reviewStatus: data.reviewStatus,
  221. reviewId: data.reviewId,
  222. actions: mapActions(data.actions)
  223. }
  224. }
  225. /** 评价详情 */
  226. export function mapOrderReview(data) {
  227. if (!data) return null
  228. const pics = (data.pics || []).map((p) => resolveFileUrl(p) || p).filter(Boolean)
  229. return {
  230. score: Number(data.score) || 0,
  231. content: data.content || '',
  232. pics,
  233. createTime: data.createTime || '',
  234. replyContent: data.replyContent || '',
  235. replyTime: data.replyTime || ''
  236. }
  237. }
  238. /** 售后列表行 */
  239. export function mapAftersaleListRow(row) {
  240. if (!row) return null
  241. const specText = (row.goodsSpec || row.specText || '').trim() || '默认'
  242. return {
  243. aftersaleId: row.aftersaleId,
  244. aftersaleNo: row.aftersaleNo || '',
  245. aftersaleStatus: row.aftersaleStatus,
  246. statusText:
  247. row.aftersaleStatusText ||
  248. row.statusText ||
  249. mapAftersaleStatusText(row.aftersaleStatus),
  250. applyType: row.applyType,
  251. applyTypeText: row.applyTypeText || mapAftersaleApplyTypeText(row.applyType),
  252. applyReason: row.applyReason || '',
  253. applyAmount: row.applyAmount,
  254. applyAmountText: formatPrice(row.applyAmount),
  255. createTime: row.createTime || '',
  256. goodsName: row.goodsName || '',
  257. displayPic: resolveFileUrl(row.goodsImage || row.mainPic) || GOODS_PLACEHOLDER,
  258. specList: parseCartSpecText(specText),
  259. orderId: row.orderId,
  260. orderNo: row.orderNo || ''
  261. }
  262. }
  263. /** 售后详情 */
  264. export function mapAftersaleDetail(data) {
  265. if (!data) return null
  266. const info = data.info || data
  267. const specText = (info.goodsSpec || info.specText || '').trim() || '默认'
  268. const progressStage = data.progressStage || info.progressStage || ''
  269. const aftersaleStatus = info.aftersaleStatus || data.aftersaleStatus
  270. return {
  271. aftersaleId: info.aftersaleId || data.aftersaleId,
  272. aftersaleNo: info.aftersaleNo || data.aftersaleNo || '',
  273. aftersaleStatus,
  274. statusText:
  275. info.aftersaleStatusText ||
  276. info.statusText ||
  277. mapAftersaleStatusText(aftersaleStatus),
  278. progress: data.progress || info.progress || progressStage,
  279. progressText:
  280. data.progressText ||
  281. info.progressText ||
  282. mapAftersaleProgressText(progressStage),
  283. applyType: info.applyType,
  284. applyTypeText: info.applyTypeText || mapAftersaleApplyTypeText(info.applyType),
  285. applyReason: info.applyReason || '',
  286. returnQuantity: info.returnQuantity,
  287. applyAmount: info.applyAmount,
  288. applyAmountText: formatPrice(info.applyAmount),
  289. description: info.description || '',
  290. evidencePics: normalizePicList(info.evidencePics).map((p) => resolveFileUrl(p) || p),
  291. createTime: info.createTime || '',
  292. finishTime: info.finishTime || '',
  293. processResult: data.processResult || info.processResult || '',
  294. orderId: info.orderId,
  295. orderNo: info.orderNo || '',
  296. orderItemId: info.orderItemId,
  297. goodsName: info.goodsName || '',
  298. displayPic: resolveFileUrl(info.goodsImage || info.mainPic) || GOODS_PLACEHOLDER,
  299. specList: parseCartSpecText(specText),
  300. quantity: Number(info.quantity) || 1
  301. }
  302. }
  303. /** 待支付倒计时文案 */
  304. export function formatPayCountdown(seconds) {
  305. if (seconds == null || seconds <= 0) return '已超时'
  306. const h = Math.floor(seconds / 3600)
  307. const m = Math.floor((seconds % 3600) / 60)
  308. const s = seconds % 60
  309. if (h > 0) {
  310. return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
  311. }
  312. return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
  313. }