refund-step-two.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. <template>
  2. <view class="refund-step-two">
  3. <view class="warning-tip">
  4. <text>确认退款信息后提交,提交后不可再次退款。</text>
  5. </view>
  6. <!-- 遍历每个选中的商品 -->
  7. <view class="card" v-for="(item, index) in localGoods" :key="index">
  8. <view class="goods-item mb-30">
  9. <image :src="item.cover" mode="aspectFill" class="goods-cover"></image>
  10. <view class="goods-info">
  11. <view class="goods-name u-line-2">{{ item.bookName }}</view>
  12. <view class="price-box mt-10">
  13. <text class="price">¥{{ item.payPrice || item.price }}</text>
  14. <view v-if="item.canModifyNum == 1" class="num-box">
  15. <u-number-box v-model="item.refundNum" :min="1" :max="item.canRefundNum" @change="calculateRefundMoney(index)"></u-number-box>
  16. </view>
  17. <text v-else class="num">x{{ item.refundNum }}</text>
  18. </view>
  19. </view>
  20. </view>
  21. <u-cell-group :border="false">
  22. <u-cell-item v-if="refundType === '1'" title="货物状态" :value="item.shopStatusText" @click="openStatusPicker(index)"></u-cell-item>
  23. <u-cell-item title="退款原因" :value="item.refundReasonText" @click="openReasonPicker(index)"></u-cell-item>
  24. <view class="money-cell">
  25. <view class="cell-left">
  26. <text class="label">退款金额</text>
  27. </view>
  28. <view class="cell-right">
  29. <view class="edit-btn" v-if="item.canModifyMoney == 1" @click="openMoneyEdit(index)">
  30. <u-icon name="edit-pen" size="28"></u-icon>
  31. <text>修改</text>
  32. </view>
  33. <text class="money-text ml-20">¥{{ item.refundMoney }}</text>
  34. </view>
  35. </view>
  36. <u-cell-item title="上传描述和凭证" :value="getUploadStatusText(item)"
  37. @click="openUploadPopup(index)" :border-bottom="false"></u-cell-item>
  38. </u-cell-group>
  39. </view>
  40. <!-- 如果是退货退款,还需要选择退货方式和地址?
  41. 原申请页面有退货方式,但图 3 并没有显示这部分,可能是在其它地方或者不需要?
  42. 我们根据原有的退货方式逻辑,在最底部或者顶部加入 -->
  43. <view class="card" v-if="refundType === '1'">
  44. <u-cell-group :border="false">
  45. <u-cell-item title="退货方式" :value="returnMethodText || '请选择'" @click="showReturnMethodPicker = true" required></u-cell-item>
  46. </u-cell-group>
  47. <view class="address-section" v-if="returnMethod === '1'" @click="chooseAddress">
  48. <view class="flex-a flex-j-b" v-if="address && address.name">
  49. <view class="address-label">取件地址</view>
  50. <view class="flex-d flex-1 ml-24" style="margin-right: 20rpx">
  51. <view class="flex-a flex-j-b mb-10">
  52. <view class="address-text">{{ address.name }}</view>
  53. <view class="address-text">{{ address.mobile }}</view>
  54. </view>
  55. <view class="address-text">{{ address.fullAddress }}</view>
  56. </view>
  57. <u-icon name="arrow-right" :size="28" color="#666" top="4"></u-icon>
  58. </view>
  59. <view class="flex-a flex-j-b" v-else>
  60. <view class="flex-a">
  61. <u-icon name="plus-circle-fill" :size="48" color="#38C148" top="2"></u-icon>
  62. <view class="ml-10 font-30 address-text">选择取件地址</view>
  63. </view>
  64. <u-icon name="arrow-right" :size="28" color="#666" top="4"></u-icon>
  65. </view>
  66. </view>
  67. </view>
  68. <view style="height: 140rpx;"></view>
  69. <!-- 底部操作栏 -->
  70. <view class="footer-bar">
  71. <view class="footer-left">
  72. <text class="total-label">共退款</text>
  73. <text class="total-money">¥{{ totalRefundMoney }}</text>
  74. <text class="total-num">共{{ totalRefundNum }}件</text>
  75. </view>
  76. <view class="footer-right">
  77. <view class="btn-wrap" style="margin-left: 30rpx;">
  78. <u-button shape="circle" :custom-style="cancelBtnStyle" @click="$emit('back')">上一步</u-button>
  79. </view>
  80. <view class="btn-wrap">
  81. <u-button type="primary" shape="circle" :custom-style="submitBtnStyle" @click="submit">提交申请</u-button>
  82. </view>
  83. </view>
  84. </view>
  85. <!-- 修改退款金额弹窗 -->
  86. <u-popup v-model="showMoneyPopup" mode="bottom" border-radius="24">
  87. <view class="popup-container">
  88. <view class="popup-header">
  89. <text class="title">修改退款金额</text>
  90. <u-icon name="close" size="32" color="#999" @click="showMoneyPopup = false"></u-icon>
  91. </view>
  92. <view class="popup-content">
  93. <view class="input-row">
  94. <text>退款金额:</text>
  95. <u-input v-model="tempMoney" type="digit" placeholder="请输入退款金额" class="money-input" />
  96. </view>
  97. <view class="max-hint">最大可退金额: ¥{{ maxMoneyForCurrent }}</view>
  98. </view>
  99. <view class="popup-footer">
  100. <u-button type="primary" shape="circle" :custom-style="submitBtnStyle" @click="confirmMoney">确认</u-button>
  101. </view>
  102. </view>
  103. </u-popup>
  104. <!-- 上传描述和凭证弹窗 -->
  105. <u-popup v-model="showUploadPopup" mode="bottom" border-radius="24" height="800">
  106. <view class="popup-container full-height">
  107. <view class="popup-header">
  108. <text class="title">上传描述和凭证</text>
  109. <u-icon name="close" size="32" color="#999" @click="showUploadPopup = false"></u-icon>
  110. </view>
  111. <view class="popup-content flex-1">
  112. <view class="upload-textarea-box">
  113. <u-input v-model="tempDesc" type="textarea" placeholder="补充描述,有助于平台更好的处理售后问题" :height="100" maxlength="200" />
  114. </view>
  115. <view class="upload-area mt-20">
  116. <common-image-upload v-model="tempFileList" :max-count="5" code="shopRefund" width="160" height="160">
  117. </common-image-upload>
  118. </view>
  119. </view>
  120. <view class="popup-footer">
  121. <u-button type="primary" shape="circle" :custom-style="submitBtnStyle" @click="confirmUpload">完成</u-button>
  122. </view>
  123. </view>
  124. </u-popup>
  125. <!-- 退货方式弹窗 -->
  126. <u-select v-model="showReturnMethodPicker" :list="returnMethodList" @confirm="confirmReturnMethod"></u-select>
  127. <!-- 退款原因弹窗 -->
  128. <u-select v-model="showReasonPicker" :list="reasonList" @confirm="confirmReason"></u-select>
  129. <!-- 货物状态弹窗 -->
  130. <u-select v-model="showStatusPicker" :list="statusList" @confirm="confirmStatus"></u-select>
  131. </view>
  132. </template>
  133. <script>
  134. import CommonImageUpload from "@/components/image-upload.vue";
  135. export default {
  136. name: 'RefundStepTwo',
  137. components: {
  138. CommonImageUpload
  139. },
  140. props: {
  141. selectedGoods: {
  142. type: Array,
  143. default: () => []
  144. },
  145. refundType: {
  146. type: String,
  147. default: '1'
  148. },
  149. defaultAddress: {
  150. type: Object,
  151. default: () => null
  152. },
  153. reasonList: {
  154. type: Array,
  155. default: () => []
  156. },
  157. statusList: {
  158. type: Array,
  159. default: () => []
  160. }
  161. },
  162. data() {
  163. return {
  164. localGoods: [],
  165. showMoneyPopup: false,
  166. showUploadPopup: false,
  167. showReasonPicker: false,
  168. showStatusPicker: false,
  169. currentIndex: -1,
  170. tempMoney: '',
  171. maxMoneyForCurrent: '0.00',
  172. tempDesc: '',
  173. tempFileList: [],
  174. returnMethod: '3',
  175. returnMethodText: '自行寄回',
  176. showReturnMethodPicker: false,
  177. returnMethodList: [
  178. { value: '1', label: '上门取件' },
  179. { value: '2', label: '寄件点自寄' },
  180. { value: '3', label: '自行寄回' }
  181. ],
  182. address: null,
  183. submitBtnStyle: {
  184. height: '70rpx',
  185. fontSize: '28rpx',
  186. backgroundColor: '#38C148',
  187. color: '#ffffff',
  188. border: 'none',
  189. padding: '0'
  190. },
  191. cancelBtnStyle: {
  192. height: '70rpx',
  193. fontSize: '28rpx',
  194. backgroundColor: '#ffffff',
  195. color: '#333',
  196. border: '1px solid #ccc',
  197. padding: '0'
  198. }
  199. };
  200. },
  201. watch: {
  202. selectedGoods: {
  203. handler(val) {
  204. this.localGoods = JSON.parse(JSON.stringify(val));
  205. },
  206. deep: true,
  207. immediate: true
  208. },
  209. defaultAddress: {
  210. handler(val) {
  211. if (val) this.address = { ...val };
  212. },
  213. deep: true,
  214. immediate: true
  215. }
  216. },
  217. computed: {
  218. totalRefundMoney() {
  219. let total = 0;
  220. this.localGoods.forEach(item => {
  221. total += Number(item.refundMoney || 0);
  222. });
  223. return total.toFixed(2);
  224. },
  225. totalRefundNum() {
  226. let total = 0;
  227. this.localGoods.forEach(item => {
  228. total += Number(item.refundNum || 0);
  229. });
  230. return total;
  231. }
  232. },
  233. methods: {
  234. calculateRefundMoney(index) {
  235. const item = this.localGoods[index];
  236. // 简单按比例或单价计算最大可退金额和当前退款金额
  237. // 此处以单价 * 数量计算作为基准。如果后台返回了具体的单件金额或算法,可以进一步优化。
  238. const unitPrice = Number(item.payPrice || item.price || 0);
  239. const newMoney = (unitPrice * item.refundNum).toFixed(2);
  240. // 同步更新 canRefundMoney (如果有的话) 和 refundMoney
  241. this.$set(item, 'refundMoney', newMoney);
  242. this.$set(item, 'canRefundMoney', newMoney);
  243. },
  244. getUploadStatusText(item) {
  245. if ((item.description && item.description.trim().length > 0) || (item.fileUrlList && item.fileUrlList.length > 0)) {
  246. return '已补充';
  247. }
  248. return '上传有助处理退款';
  249. },
  250. openMoneyEdit(index) {
  251. this.currentIndex = index;
  252. const item = this.localGoods[index];
  253. this.tempMoney = String(item.refundMoney);
  254. this.maxMoneyForCurrent = String(item.canRefundMoney || 0);
  255. this.showMoneyPopup = true;
  256. },
  257. confirmMoney() {
  258. let val = Number(this.tempMoney);
  259. let max = Number(this.maxMoneyForCurrent);
  260. if (val < 0) val = 0;
  261. if (val > max) val = max;
  262. this.$set(this.localGoods[this.currentIndex], 'refundMoney', val.toFixed(2));
  263. this.showMoneyPopup = false;
  264. },
  265. openUploadPopup(index) {
  266. this.currentIndex = index;
  267. const item = this.localGoods[index];
  268. this.tempDesc = item.description || '';
  269. this.tempFileList = item.fileUrlList ? [...item.fileUrlList] : [];
  270. this.showUploadPopup = true;
  271. },
  272. confirmUpload() {
  273. const item = this.localGoods[this.currentIndex];
  274. this.$set(item, 'description', this.tempDesc);
  275. this.$set(item, 'fileUrlList', [...this.tempFileList]);
  276. this.showUploadPopup = false;
  277. },
  278. openStatusPicker(index) {
  279. this.currentIndex = index;
  280. this.showStatusPicker = true;
  281. },
  282. openReasonPicker(index) {
  283. this.currentIndex = index;
  284. this.showReasonPicker = true;
  285. },
  286. confirmStatus(e) {
  287. const item = this.localGoods[this.currentIndex];
  288. this.$set(item, 'shopStatus', e[0].value);
  289. this.$set(item, 'shopStatusText', e[0].label);
  290. },
  291. confirmReason(e) {
  292. const item = this.localGoods[this.currentIndex];
  293. this.$set(item, 'refundReason', e[0].value);
  294. this.$set(item, 'refundReasonText', e[0].label);
  295. },
  296. confirmReturnMethod(e) {
  297. this.returnMethod = e[0].value;
  298. this.returnMethodText = e[0].label;
  299. },
  300. chooseAddress() {
  301. uni.navigateTo({
  302. url: `/pages-mine/pages/address/list?id=${this.address ? this.address.id : ''}&isSelect=1`
  303. });
  304. },
  305. updateAddress(addr) {
  306. this.address = addr;
  307. },
  308. submit() {
  309. if (this.refundType === '1') {
  310. if (!this.returnMethod) {
  311. this.$u.toast('请选择退货方式');
  312. return;
  313. }
  314. if (this.returnMethod === '1' && (!this.address || !this.address.name)) {
  315. this.$u.toast('请选择取件地址');
  316. return;
  317. }
  318. }
  319. this.$emit('submit', {
  320. goodsList: this.localGoods,
  321. returnMethod: this.returnMethod,
  322. address: this.address
  323. });
  324. }
  325. }
  326. }
  327. </script>
  328. <style lang="scss" scoped>
  329. .refund-step-two {
  330. padding-bottom: 140rpx;
  331. }
  332. .warning-tip {
  333. padding: 20rpx 30rpx;
  334. color: #38C148;
  335. font-size: 24rpx;
  336. background-color: #d1f2d8;
  337. margin-bottom: 20rpx;
  338. }
  339. .card {
  340. background-color: #fff;
  341. border-radius: 16rpx;
  342. margin: 0 20rpx 20rpx;
  343. }
  344. .goods-item {
  345. display: flex;
  346. align-items: center;
  347. padding: 30rpx;
  348. padding-bottom: 0;
  349. .goods-cover {
  350. width: 120rpx;
  351. height: 120rpx;
  352. border-radius: 8rpx;
  353. margin-right: 20rpx;
  354. }
  355. .goods-info {
  356. flex: 1;
  357. display: flex;
  358. flex-direction: column;
  359. justify-content: space-between;
  360. .goods-name {
  361. font-size: 28rpx;
  362. color: #333;
  363. line-height: 1.4;
  364. }
  365. .price-box {
  366. display: flex;
  367. justify-content: space-between;
  368. align-items: center;
  369. .price {
  370. font-size: 28rpx;
  371. color: #333;
  372. font-weight: 500;
  373. }
  374. .num {
  375. font-size: 26rpx;
  376. color: #999;
  377. }
  378. }
  379. }
  380. }
  381. .mb-30 {
  382. margin-bottom: 30rpx;
  383. }
  384. .mt-10 {
  385. margin-top: 10rpx;
  386. }
  387. .ml-20 {
  388. margin-left: 20rpx;
  389. }
  390. .money-cell {
  391. display: flex;
  392. justify-content: space-between;
  393. align-items: center;
  394. padding: 26rpx 32rpx;
  395. background-color: #fff;
  396. border-bottom: 1rpx solid #f5f5f5;
  397. .cell-left {
  398. .label {
  399. font-size: 28rpx;
  400. color: #333;
  401. }
  402. }
  403. .cell-right {
  404. display: flex;
  405. align-items: center;
  406. .edit-btn {
  407. display: flex;
  408. align-items: center;
  409. color: #666;
  410. font-size: 24rpx;
  411. margin-right: 10rpx;
  412. text {
  413. margin-left: 4rpx;
  414. }
  415. }
  416. .money-text {
  417. font-size: 32rpx;
  418. color: #38C148;
  419. font-weight: bold;
  420. }
  421. }
  422. }
  423. .footer-bar {
  424. position: fixed;
  425. bottom: 0;
  426. left: 0;
  427. right: 0;
  428. background-color: #fff;
  429. display: flex;
  430. align-items: center;
  431. justify-content: space-between;
  432. padding: 24rpx 30rpx;
  433. padding-bottom: 0;
  434. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  435. z-index: 100;
  436. padding-bottom: constant(safe-area-inset-bottom);
  437. padding-bottom: env(safe-area-inset-bottom);
  438. .footer-left {
  439. display: flex;
  440. align-items: baseline;
  441. .total-label {
  442. font-size: 28rpx;
  443. color: #38C148;
  444. font-weight: bold;
  445. }
  446. .total-money {
  447. font-size: 36rpx;
  448. color: #38C148;
  449. font-weight: bold;
  450. margin: 0 10rpx;
  451. }
  452. .total-num {
  453. font-size: 24rpx;
  454. color: #999;
  455. }
  456. }
  457. .footer-right {
  458. display: flex;
  459. align-items: center;
  460. flex: 1;
  461. justify-content: flex-end;
  462. gap: 40rpx;
  463. .btn-wrap {
  464. flex: 1;
  465. max-width: 200rpx;
  466. }
  467. }
  468. }
  469. .popup-container {
  470. padding: 30rpx;
  471. background-color: #fff;
  472. display: flex;
  473. flex-direction: column;
  474. &.full-height {
  475. height: 100%;
  476. }
  477. .popup-header {
  478. display: flex;
  479. justify-content: space-between;
  480. align-items: center;
  481. margin-bottom: 40rpx;
  482. flex-shrink: 0;
  483. .title {
  484. font-size: 32rpx;
  485. font-weight: bold;
  486. color: #333;
  487. }
  488. }
  489. .popup-content {
  490. margin-bottom: 40rpx;
  491. &.flex-1 {
  492. flex: 1;
  493. overflow-y: auto;
  494. }
  495. .input-row {
  496. display: flex;
  497. align-items: center;
  498. margin-bottom: 20rpx;
  499. font-size: 30rpx;
  500. .money-input {
  501. flex: 1;
  502. text-align: right;
  503. }
  504. }
  505. .max-hint {
  506. font-size: 24rpx;
  507. color: #999;
  508. text-align: right;
  509. }
  510. .upload-textarea-box {
  511. background-color: #f9f9f9;
  512. padding: 20rpx;
  513. border-radius: 12rpx;
  514. }
  515. }
  516. .popup-footer {
  517. flex-shrink: 0;
  518. padding-bottom: constant(safe-area-inset-bottom);
  519. padding-bottom: env(safe-area-inset-bottom);
  520. }
  521. }
  522. .address-section {
  523. padding: 26rpx 0;
  524. border-top: 1rpx solid #f5f5f5;
  525. }
  526. .address-text {
  527. color: #333333;
  528. font-family: PingFang SC;
  529. font-weight: 400;
  530. }
  531. .address-label {
  532. font-size: 28rpx;
  533. color: #909399;
  534. margin-right: 20rpx;
  535. width: 140rpx;
  536. }
  537. .mt-20 {
  538. margin-top: 20rpx;
  539. }
  540. .flex-1 {
  541. flex: 1;
  542. }
  543. </style>