refund-step-one.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <template>
  2. <view class="refund-step-one">
  3. <view class="card">
  4. <view class="type-section">
  5. <view class="section-title">选择售后类型</view>
  6. <view class="type-btn-group">
  7. <view class="type-btn" :class="{ active: localRefundType === '1' }"
  8. @click="changeRefundType('1')">
  9. <text>退货退款</text>
  10. </view>
  11. <view class="type-btn" :class="{ active: localRefundType === '2' }"
  12. @click="changeRefundType('2')">
  13. <text>仅退款</text>
  14. </view>
  15. </view>
  16. </view>
  17. <view class="card-header">
  18. <text class="card-title">选择售后商品</text>
  19. <text class="sub-title">已选 {{ selectedCount }}/{{ goodsList.length }}</text>
  20. </view>
  21. <view class="goods-list">
  22. <view v-for="(item, index) in localGoodsList" :key="index" class="goods-item" :class="{ 'is-disabled': item.disabled }" @click="toggleCheck(index)">
  23. <view class="checkbox-box">
  24. <u-icon v-if="item.disabled" name="minus-circle" color="#ccc" size="44"></u-icon>
  25. <u-icon v-else-if="item.checked" name="checkmark-circle-fill" color="#38C148" size="44"></u-icon>
  26. <u-icon v-else name="checkmark-circle" color="#ccc" size="44"></u-icon>
  27. </view>
  28. <image :src="item.cover" mode="aspectFill" class="goods-cover"></image>
  29. <view class="goods-info">
  30. <view class="goods-name u-line-2">{{ item.bookName }}</view>
  31. <view class="goods-sku" v-if="item.isbn">ISBN: {{ item.isbn }}</view>
  32. <view class="goods-sku" v-if="item.conditionType">品相:{{ item.conditionType | conditionText }}</view>
  33. <view class="price-box">
  34. <text class="price">¥{{ item.payPrice || item.price }}</text>
  35. <view class="num-wrap">
  36. <text class="status-tag" v-if="item.status == '2'">退款中</text>
  37. <text class="status-tag" v-if="item.status == '3'">退款成功</text>
  38. <text class="num">x{{ item.refundNum || item.num || 1 }}</text>
  39. </view>
  40. </view>
  41. </view>
  42. </view>
  43. </view>
  44. </view>
  45. <view class="card">
  46. <view class="card-header" style="border-bottom: none; padding-bottom: 0;">
  47. <text class="card-title">选择售后原因</text>
  48. </view>
  49. <!-- 货物状态选择 (如果是退货退款) -->
  50. <view v-if="localRefundType === '1'" class="status-tabs">
  51. <view class="status-tab" :class="{ active: globalShopStatus === '1' }" @click="globalShopStatus = '1'">未收到货</view>
  52. <view class="status-tab" :class="{ active: globalShopStatus === '2' }" @click="globalShopStatus = '2'">已收到货</view>
  53. </view>
  54. <view class="reason-list">
  55. <view class="reason-item" v-for="(reason, index) in reasonList" :key="index" @click="selectReason(reason)">
  56. <text class="reason-label">{{ reason.label }}</text>
  57. <u-icon v-if="globalRefundReason === reason.label" name="checkmark-circle-fill" color="#38C148" size="44"></u-icon>
  58. <u-icon v-else name="checkmark-circle" color="#ccc" size="44"></u-icon>
  59. </view>
  60. </view>
  61. </view>
  62. <view class="footer-bar two-btns">
  63. <view class="btn-wrap">
  64. <u-button shape="circle" :custom-style="cancelBtnStyle" @click="cancelRefund">先不退了</u-button>
  65. </view>
  66. <view class="btn-wrap">
  67. <u-button type="primary" shape="circle" :custom-style="btnStyle" @click="goNextInnerStep">下一步</u-button>
  68. </view>
  69. </view>
  70. </view>
  71. </template>
  72. <script>
  73. export default {
  74. name: 'RefundStepOne',
  75. props: {
  76. goodsList: {
  77. type: Array,
  78. default: () => []
  79. },
  80. refundType: {
  81. type: String,
  82. default: '1'
  83. },
  84. reasonList: {
  85. type: Array,
  86. default: () => []
  87. },
  88. statusList: {
  89. type: Array,
  90. default: () => [
  91. { value: '1', label: '未收到货' },
  92. { value: '2', label: '已收到货' }
  93. ]
  94. }
  95. },
  96. data() {
  97. return {
  98. localRefundType: this.refundType,
  99. localGoodsList: [],
  100. globalShopStatus: '1',
  101. globalShopStatusText: '未收到货',
  102. globalRefundReason: '',
  103. globalRefundReasonText: '',
  104. btnStyle: {
  105. width: '100%',
  106. height: '80rpx',
  107. fontSize: '30rpx',
  108. backgroundColor: '#38C148',
  109. color: '#ffffff',
  110. border: 'none'
  111. },
  112. cancelBtnStyle: {
  113. width: '100%',
  114. height: '80rpx',
  115. fontSize: '30rpx',
  116. backgroundColor: '#ffffff',
  117. color: '#333',
  118. border: '1px solid #f0f0f0'
  119. }
  120. };
  121. },
  122. watch: {
  123. goodsList: {
  124. handler(val) {
  125. // 深度拷贝,避免直接修改 props
  126. this.localGoodsList = JSON.parse(JSON.stringify(val));
  127. // 自动回填售后原因(如果是修改申请且已存在全局原因或第一件商品有原因)
  128. if (val.length > 0 && val[0].refundReason) {
  129. this.globalRefundReason = val[0].refundReason;
  130. this.globalRefundReasonText = val[0].refundReasonText || val[0].refundReason;
  131. }
  132. if (val.length > 0 && val[0].shopStatus) {
  133. this.globalShopStatus = val[0].shopStatus;
  134. this.globalShopStatusText = val[0].shopStatusText;
  135. }
  136. },
  137. deep: true,
  138. immediate: true
  139. },
  140. refundType(val) {
  141. this.localRefundType = val;
  142. this.globalShopStatus = val === '1' ? '2' : '1';
  143. },
  144. globalShopStatus(val) {
  145. this.globalShopStatusText = val === '1' ? '未收到货' : '已收到货';
  146. }
  147. },
  148. computed: {
  149. selectedCount() {
  150. return this.localGoodsList.filter(item => item.checked).length;
  151. },
  152. selectedGoods() {
  153. return this.localGoodsList.filter(item => item.checked);
  154. }
  155. },
  156. methods: {
  157. changeRefundType(type) {
  158. if (this.localRefundType !== type) {
  159. this.localRefundType = type;
  160. this.$emit('update:refundType', type);
  161. }
  162. },
  163. toggleCheck(index) {
  164. if (this.localGoodsList[index].disabled) {
  165. this.$u.toast('该商品状态不可退款');
  166. return;
  167. }
  168. this.$set(this.localGoodsList[index], 'checked', !this.localGoodsList[index].checked);
  169. },
  170. goNextInnerStep() {
  171. if (this.selectedCount === 0) {
  172. this.$u.toast('请选择售后商品');
  173. return;
  174. }
  175. if (!this.globalRefundReason) {
  176. this.$u.toast('请选择售后原因');
  177. return;
  178. }
  179. // 将选定的全局原因和货物状态填充到每一个被选中的商品身上
  180. this.selectedGoods.forEach(item => {
  181. this.$set(item, 'shopStatus', this.localRefundType === '1' ? this.globalShopStatus : '1');
  182. this.$set(item, 'shopStatusText', this.localRefundType === '1' ? this.globalShopStatusText : '未收到货');
  183. this.$set(item, 'refundReason', this.globalRefundReason);
  184. this.$set(item, 'refundReasonText', this.globalRefundReasonText);
  185. });
  186. // 直接派发 next 事件到第二步
  187. this.$emit('next', {
  188. selectedGoods: this.selectedGoods,
  189. refundType: this.localRefundType
  190. });
  191. },
  192. selectReason(reason) {
  193. this.globalRefundReason = reason.label;
  194. this.globalRefundReasonText = reason.label;
  195. },
  196. cancelRefund() {
  197. uni.navigateBack();
  198. }
  199. }
  200. }
  201. </script>
  202. <style lang="scss" scoped>
  203. .refund-step-one {
  204. padding-bottom: 140rpx;
  205. }
  206. .card {
  207. background-color: #fff;
  208. border-radius: 16rpx;
  209. margin-bottom: 20rpx;
  210. padding: 30rpx;
  211. .goods-list {
  212. .goods-item {
  213. display: flex;
  214. align-items: center;
  215. margin-bottom: 30rpx;
  216. transition: opacity 0.3s;
  217. &.is-disabled {
  218. opacity: 0.5;
  219. }
  220. &:last-child {
  221. margin-bottom: 0;
  222. }
  223. }
  224. }
  225. .type-section {
  226. margin-bottom: 30rpx;
  227. .section-title {
  228. font-size: 30rpx;
  229. font-weight: bold;
  230. color: #333;
  231. margin-bottom: 20rpx;
  232. }
  233. .type-btn-group {
  234. display: flex;
  235. .type-btn {
  236. flex: 1;
  237. height: 64rpx;
  238. display: flex;
  239. align-items: center;
  240. justify-content: center;
  241. border: 2rpx solid #e5e5e5;
  242. border-radius: 8rpx;
  243. margin-right: 20rpx;
  244. font-size: 28rpx;
  245. color: #333;
  246. transition: all 0.3s;
  247. &:last-child {
  248. margin-right: 0;
  249. }
  250. &.active {
  251. border-color: #38C148;
  252. color: #38C148;
  253. background-color: rgba(56, 193, 72, 0.05);
  254. font-weight: bold;
  255. }
  256. }
  257. }
  258. }
  259. .card-header {
  260. padding: 10rpx 0 20rpx;
  261. border-bottom: 1rpx solid #f5f5f5;
  262. margin-bottom: 20rpx;
  263. display: flex;
  264. align-items: center;
  265. .card-title {
  266. font-size: 30rpx;
  267. font-weight: bold;
  268. color: #333;
  269. margin-right: 20rpx;
  270. }
  271. .sub-title {
  272. font-size: 24rpx;
  273. color: #38C148;
  274. }
  275. }
  276. .status-tabs {
  277. display: flex;
  278. padding: 20rpx 0;
  279. .status-tab {
  280. flex: 1;
  281. height: 72rpx;
  282. display: flex;
  283. justify-content: center;
  284. align-items: center;
  285. background-color: #f5f5f5;
  286. color: #333;
  287. font-size: 28rpx;
  288. border-radius: 36rpx;
  289. margin-right: 20rpx;
  290. transition: all 0.3s;
  291. &:last-child {
  292. margin-right: 0;
  293. }
  294. &.active {
  295. background-color: rgba(56, 193, 72, 0.1);
  296. color: #38C148;
  297. font-weight: bold;
  298. border: 1px solid #38C148;
  299. }
  300. }
  301. }
  302. .reason-list {
  303. .reason-item {
  304. display: flex;
  305. justify-content: space-between;
  306. align-items: center;
  307. padding: 30rpx 0;
  308. border-bottom: 1rpx solid #f5f5f5;
  309. &:last-child {
  310. border-bottom: none;
  311. }
  312. .reason-label {
  313. font-size: 30rpx;
  314. color: #333;
  315. }
  316. }
  317. }
  318. }
  319. .goods-list {
  320. .goods-item {
  321. display: flex;
  322. align-items: center;
  323. margin-bottom: 30rpx;
  324. &:last-child {
  325. margin-bottom: 0;
  326. }
  327. .checkbox-box {
  328. margin-right: 20rpx;
  329. }
  330. }
  331. }
  332. .goods-item {
  333. display: flex;
  334. align-items: center;
  335. .goods-cover {
  336. width: 140rpx;
  337. height: 150rpx;
  338. border-radius: 8rpx;
  339. margin-right: 20rpx;
  340. }
  341. .goods-info {
  342. flex: 1;
  343. height: 160rpx;
  344. display: flex;
  345. flex-direction: column;
  346. justify-content: space-between;
  347. .goods-name {
  348. font-size: 28rpx;
  349. color: #333;
  350. line-height: 1.4;
  351. }
  352. .goods-sku {
  353. font-size: 24rpx;
  354. color: #999;
  355. }
  356. .price-box {
  357. display: flex;
  358. justify-content: space-between;
  359. align-items: center;
  360. margin-top: 10rpx;
  361. .price {
  362. font-size: 32rpx;
  363. font-weight: bold;
  364. color: #333;
  365. }
  366. .num-wrap {
  367. display: flex;
  368. align-items: center;
  369. .status-tag {
  370. font-size: 22rpx;
  371. color: #ff3b30;
  372. border: 1rpx solid #ff3b30;
  373. padding: 2rpx 10rpx;
  374. border-radius: 6rpx;
  375. margin-right: 10rpx;
  376. }
  377. .num {
  378. font-size: 26rpx;
  379. color: #999;
  380. }
  381. }
  382. }
  383. }
  384. }
  385. .mb-30 {
  386. margin-bottom: 30rpx;
  387. }
  388. .mt-10 {
  389. margin-top: 10rpx;
  390. }
  391. .footer-bar {
  392. position: fixed;
  393. bottom: 0;
  394. left: 0;
  395. right: 0;
  396. background-color: #fff;
  397. display: flex;
  398. align-items: center;
  399. padding: 24rpx 30rpx;
  400. padding-bottom: 0;
  401. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  402. z-index: 100;
  403. padding-bottom: constant(safe-area-inset-bottom);
  404. padding-bottom: env(safe-area-inset-bottom);
  405. &.two-btns {
  406. display: flex;
  407. justify-content: space-between;
  408. align-items: center;
  409. gap: 40rpx;
  410. .btn-wrap {
  411. flex: 1;
  412. }
  413. }
  414. }
  415. </style>