apply-refund.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  1. <template>
  2. <view class="apply-refund-page">
  3. <!-- 退款商品 -->
  4. <view class="card">
  5. <view class="type-section">
  6. <view class="section-title">请选择售后类型</view>
  7. <view class="type-btn-group">
  8. <view class="type-btn" :class="{ active: refundType === '1' }"
  9. @click="confirmType([{ value: '1', label: '退货退款' }])">
  10. <text>退货退款</text>
  11. </view>
  12. <view class="type-btn" :class="{ active: refundType === '2' }"
  13. @click="confirmType([{ value: '2', label: '仅退款' }])">
  14. <text>仅退款</text>
  15. </view>
  16. </view>
  17. </view>
  18. <view class="card-header">
  19. <text class="card-title">退款商品</text>
  20. </view>
  21. <view class="goods-list">
  22. <view v-for="(item, index) in orderInfo.detailVoList" :key="index" class="goods-item">
  23. <view class="checkbox-box" @click.stop="toggleCheck(item)">
  24. <u-icon v-if="item.checked" name="checkmark-circle-fill" color="#38C148" size="44"></u-icon>
  25. <u-icon v-else name="checkmark-circle" color="#ccc" size="44"></u-icon>
  26. </view>
  27. <image :src="item.cover" mode="aspectFill" class="goods-cover"></image>
  28. <view class="goods-info">
  29. <view class="goods-name u-line-2">{{ item.bookName }}</view>
  30. <view class="goods-sku" v-if="item.isbn">ISBN: {{ item.isbn }}</view>
  31. <view class="price-box">
  32. <text class="price">¥{{ item.payPrice }}</text>
  33. <u-number-box v-model="item.refundNum" :min="1" :max="item.num"
  34. @change="calculateRefundMoney"></u-number-box>
  35. </view>
  36. </view>
  37. </view>
  38. </view>
  39. </view>
  40. <!-- 退款原因配置 -->
  41. <view class="card no-padding">
  42. <u-cell-group :border="false">
  43. <!-- 退款原因 -->
  44. <u-cell-item title="退款原因" :value="refundReasonText || '请选择'" @click="showReasonPicker = true" required
  45. :border-bottom="refundType === '2' ? false : true"></u-cell-item>
  46. <!-- 货物状态 (仅退款显示) -->
  47. <u-cell-item v-if="refundType === '1'" title="货物状态" :value="shopStatusText"
  48. @click="showStatusPicker = true" required :border-bottom="false"></u-cell-item>
  49. </u-cell-group>
  50. </view>
  51. <!-- 退款金额与凭证 -->
  52. <view class="card no-padding">
  53. <view class="card-title" style="padding:30rpx 0 0 20rpx">退款金额:</view>
  54. <u-cell-group :border="false">
  55. <!-- 退回支付渠道 -->
  56. <u-cell-item :title="refundChannelText" :arrow="false">
  57. <view slot="right-icon" class="refund-money-box" @click="showRefundAmountPopup = true">
  58. <text class="money">¥{{ refundMoney }}</text>
  59. <view class="edit-tag" v-if="refundType === '2'">
  60. <u-icon name="edit-pen" size="24"></u-icon>
  61. <text>修改</text>
  62. </view>
  63. </view>
  64. </u-cell-item>
  65. <!-- 上传描述和凭证 -->
  66. <u-cell-item title="上传描述和凭证" :value="uploadStatusText" @click="showUploadPopup = true" is-link
  67. :border-bottom="false"></u-cell-item>
  68. </u-cell-group>
  69. </view>
  70. <!-- 退货方式 (仅退货退款显示) - 单独分区 -->
  71. <view class="card no-padding" v-if="refundType === '1'">
  72. <u-cell-group :border="false">
  73. <u-cell-item title="退货方式" :value="returnMethodText || '请选择'" @click="showReturnMethodPicker = true"
  74. required></u-cell-item>
  75. <!-- 上门取件地址 (仅上门取件显示) -->
  76. <view class="address-section" @click="chooseAddress">
  77. <view class="flex-a flex-j-b mb-20" v-if="address.name">
  78. <view class="address-label">我的地址</view>
  79. <image src="/pages-mine/static/adderss.png" style="width: 40rpx; height: 40rpx"></image>
  80. <view class="flex-d flex-1 ml-24" style="margin-right: 20rpx">
  81. <view class="flex-a flex-j-b mb-10">
  82. <view class="address-text">{{ address.name }}</view>
  83. <view class="address-text">{{ address.mobile }}</view>
  84. </view>
  85. <view class="address-text">{{ address.province || '' }}{{ address.city || '' }}{{
  86. address.area || '' }}{{ address.fullAddress }}</view>
  87. </view>
  88. <u-icon name="arrow-right" :size="28" color="#666" top="4"></u-icon>
  89. </view>
  90. <view class="flex-a flex-j-b" v-else>
  91. <view class="flex-a">
  92. <u-icon name="plus-circle-fill" :size="48" color="#38C148" top="2"></u-icon>
  93. <view class="ml-10 font-30 address-text">我的地址</view>
  94. <text class="u-required">*</text>
  95. </view>
  96. <view class="flex-a">
  97. <view class="ml-10 address-text">请添加</view>
  98. <u-icon name="arrow-right" :size="28" color="#666" top="4"></u-icon>
  99. </view>
  100. </view>
  101. </view>
  102. </u-cell-group>
  103. </view>
  104. <!-- 我的服务 - 单独分区 -->
  105. <view class="card no-padding">
  106. <view class="card-title" style="padding:30rpx 0 0 20rpx">我的服务</view>
  107. <u-cell-group :border="false">
  108. <u-cell-item title="退换无忧" value="服务生效中" :arrow="true" :border-bottom="false">
  109. <u-icon slot="icon" name="checkmark-circle-fill" color="#38C148" size="32"
  110. style="margin-right: 10rpx;"></u-icon>
  111. </u-cell-item>
  112. </u-cell-group>
  113. </view>
  114. <!-- 底部占位 -->
  115. <view style="height: 60rpx;"></view>
  116. <!-- 底部固定栏 -->
  117. <view class="footer-bar">
  118. <view class="footer-left" @click="showRefundAmountPopup = true">
  119. <text class="label">{{ refundChannelText }}:</text>
  120. <text class="amount">¥{{ refundMoney }}</text>
  121. <text class="detail-link">明细</text>
  122. </view>
  123. <view class="footer-right">
  124. <u-button type="primary" shape="circle" :custom-style="submitBtnStyle" @click="submit">提交申请</u-button>
  125. </view>
  126. </view>
  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. <!-- 弹窗: 退货方式 -->
  132. <u-select v-model="showReturnMethodPicker" :list="returnMethodList" :default-value="returnMethodIndex"
  133. @confirm="confirmReturnMethod"></u-select>
  134. <!-- 弹窗: 上传描述和凭证 -->
  135. <u-popup v-model="showUploadPopup" mode="bottom" border-radius="24" height="800">
  136. <view class="popup-container full-height">
  137. <view class="popup-header">
  138. <text class="title">上传描述和凭证</text>
  139. <u-icon name="close" size="32" color="#999" @click="showUploadPopup = false"></u-icon>
  140. </view>
  141. <view class="popup-content">
  142. <view class="upload-textarea-box">
  143. <u-input v-model="description" type="textarea" placeholder="补充描述,有助于平台更好的处理售后问题" :height="100"
  144. maxlength="200" />
  145. </view>
  146. <view class="upload-area">
  147. <common-image-upload v-model="fileList" :max-count="5" code="shopRefund" width="160"
  148. height="160">
  149. </common-image-upload>
  150. </view>
  151. </view>
  152. <view class="popup-footer safe-area-bottom">
  153. <u-button type="primary" shape="circle" :custom-style="submitBtnStyle"
  154. @click="showUploadPopup = false">完成</u-button>
  155. </view>
  156. </view>
  157. </u-popup>
  158. <!-- 弹窗: 退款明细 -->
  159. <u-popup v-model="showRefundAmountPopup" mode="bottom" border-radius="24">
  160. <view class="popup-container">
  161. <view class="popup-header">
  162. <text class="title">{{ refundType === '2' ? '修改退款金额' : '退款金额明细' }}</text>
  163. <u-icon name="close" size="32" color="#999" @click="showRefundAmountPopup = false"></u-icon>
  164. </view>
  165. <view class="popup-content">
  166. <view class="detail-item">
  167. <view class="item-row">
  168. <u-icon name="rmb-circle" size="36" color="#666"></u-icon>
  169. <text class="item-label">{{ refundChannelText }}</text>
  170. <text v-if="refundType !== '2'" class="item-value">¥{{ refundMoney }}</text>
  171. <u-input v-else v-model="customRefundMoney" type="number" placeholder="请输入退款金额"
  172. class="money-input" @blur="validateRefundMoney" />
  173. </view>
  174. <view v-if="refundType === '2'" class="max-money-hint">
  175. <text>最大可退金额: ¥{{ maxRefundMoney }}</text>
  176. </view>
  177. </view>
  178. <!-- 如果有红包/优惠,展示在这里 -->
  179. <view class="detail-item" v-if="otherRefundAmount > 0">
  180. <view class="item-row">
  181. <u-icon name="red-packet" size="36" color="#666"></u-icon>
  182. <text class="item-label">退回其他</text>
  183. </view>
  184. <view class="sub-list">
  185. <view class="sub-item" v-if="orderInfo.discountMoney > 0">
  186. <text class="sub-label">优惠金额</text>
  187. <text class="sub-value">¥{{ orderInfo.discountMoney }}</text>
  188. </view>
  189. <!-- 预留红包字段 -->
  190. <view class="sub-item" v-if="orderInfo.redPacketMoney > 0">
  191. <text class="sub-label">红包</text>
  192. <text class="sub-value">¥{{ orderInfo.redPacketMoney }}</text>
  193. </view>
  194. </view>
  195. </view>
  196. </view>
  197. <view class="popup-footer safe-area-bottom">
  198. <u-button v-if="refundType === '2'" type="primary" shape="circle" :custom-style="submitBtnStyle"
  199. @click="confirmRefundMoney">确认修改</u-button>
  200. <u-button v-else type="primary" shape="circle" :custom-style="submitBtnStyle"
  201. @click="showRefundAmountPopup = false">我知道了</u-button>
  202. </view>
  203. </view>
  204. </u-popup>
  205. </view>
  206. </template>
  207. <script>
  208. import CommonImageUpload from "@/components/image-upload.vue";
  209. export default {
  210. components: {
  211. CommonImageUpload
  212. },
  213. data() {
  214. return {
  215. orderId: '',
  216. isModify: false, // 是否是修改申请模式
  217. refundOrderId: '', // 修改模式下的退款单ID
  218. orderInfo: {
  219. detailVoList: [],
  220. discountMoney: 0,
  221. redPacketMoney: 0 // 假设有
  222. },
  223. // 表单数据
  224. refundType: '1', // 1: 退货退款, 2: 仅退款
  225. refundTypeText: '退货退款',
  226. refundReason: '',
  227. refundReasonText: '',
  228. shopStatus: '2', // 1: 未收到货, 2: 已收到货
  229. shopStatusText: '已收到货',
  230. returnMethod: '3', // 1: 上门取件, 2: 寄件点自寄, 3: 自行寄回
  231. returnMethodText: '自行寄回',
  232. description: '',
  233. fileList: [], // 用于回显和同步状态
  234. address: {}, // 上门取件地址
  235. // 退款金额相关
  236. customRefundMoney: '', // 用户自定义的退款金额
  237. // 辅助数据
  238. showReasonPicker: false,
  239. showStatusPicker: false,
  240. showReturnMethodPicker: false,
  241. showRefundAmountPopup: false,
  242. showUploadPopup: false, // 上传弹窗
  243. reasonList: [],
  244. statusList: [
  245. { value: '1', label: '未收到货' },
  246. { value: '2', label: '已收到货' }
  247. ],
  248. returnMethodList: [
  249. { value: '1', label: '上门取件' },
  250. { value: '2', label: '寄件点自寄' },
  251. { value: '3', label: '自行寄回' }
  252. ],
  253. submitBtnStyle: {
  254. width: '100%',
  255. height: '80rpx',
  256. fontSize: '30rpx',
  257. backgroundColor: '#38C148',
  258. color: '#ffffff'
  259. }
  260. };
  261. },
  262. computed: {
  263. returnMethodIndex() {
  264. const index = this.returnMethodList.findIndex(item => item.value === this.returnMethod);
  265. return index > -1 ? [index] : [2];
  266. },
  267. selectedGoods() {
  268. return this.orderInfo.detailVoList.filter(item => item.checked);
  269. },
  270. maxRefundMoney() {
  271. if (!this.orderInfo.detailVoList || this.orderInfo.detailVoList.length === 0) return '0.00';
  272. let totalOriginal = 0;
  273. this.orderInfo.detailVoList.forEach(item => {
  274. totalOriginal += Number(item.payPrice) * item.num;
  275. });
  276. if (totalOriginal === 0) return '0.00';
  277. let refund = 0;
  278. this.selectedGoods.forEach(item => {
  279. refund += Number(item.payPrice) * item.refundNum;
  280. });
  281. return refund.toFixed(2);
  282. },
  283. refundMoney() {
  284. if (this.customRefundMoney) {
  285. return this.customRefundMoney;
  286. }
  287. return this.maxRefundMoney;
  288. },
  289. refundChannelText() {
  290. const type = String(this.orderInfo.payType);
  291. if (type === '1') return '退回余额';
  292. if (type === '2') return '退回微信';
  293. return '退回支付渠道';
  294. },
  295. otherRefundAmount() {
  296. return (Number(this.orderInfo.discountMoney) || 0) + (Number(this.orderInfo.redPacketMoney) || 0);
  297. },
  298. uploadStatusText() {
  299. if ((this.description && this.description.trim().length > 0) || this.fileList.length > 0) {
  300. return '已补充';
  301. }
  302. return '上传有助于处理退款';
  303. }
  304. },
  305. onLoad(options) {
  306. this.getRefundReasons();
  307. if (options.isModify == '1' && options.refundOrderId) {
  308. this.isModify = true;
  309. this.refundOrderId = options.refundOrderId;
  310. this.loadRefundDetail();
  311. } else if (options.orderId) {
  312. this.orderId = options.orderId;
  313. this.loadOrderDetail();
  314. }
  315. },
  316. onShow() {
  317. let selectAddr = uni.getStorageSync("selectAddr");
  318. if (selectAddr) {
  319. this.address = selectAddr;
  320. uni.removeStorageSync("selectAddr");
  321. }
  322. },
  323. onUnload() {
  324. },
  325. methods: {
  326. loadRefundDetail() {
  327. // 使用退款详情接口回填数据
  328. this.$u.api.getNewRefundOrderDetailAjax({
  329. refundOrderId: this.refundOrderId
  330. }).then(res => {
  331. if (res.code === 200) {
  332. const data = res.data;
  333. this.orderId = data.originOrderId; // 确保orderId存在
  334. // 构造页面需要的 orderInfo 结构
  335. // 注意:退款详情接口返回的 detailList 结构可能与订单详情的 detailVoList 略有不同,需要映射
  336. this.orderInfo = {
  337. ...data,
  338. detailVoList: data.detailList.map(item => ({
  339. ...item,
  340. num: item.num, // 原购买数量
  341. refundNum: item.refundNum // 之前申请的退款数量
  342. }))
  343. };
  344. console.log(this.orderInfo, 'orderInfo');
  345. // 回填表单数据
  346. this.refundType = String(data.refundType);
  347. // this.refundTypeText 需要根据 options 查找或直接设置,这里简单根据值设置
  348. this.refundTypeText = this.refundType === '1' ? '退货退款' : '仅退款';
  349. this.shopStatus = String(data.shopStatus);
  350. this.shopStatusText = this.shopStatus === '1' ? '未收到货' : '已收到货';
  351. this.refundReason = data.refundReason;
  352. this.refundReasonText = data.refundReason; // 假设后端直接返回文本,或者需要遍历 reasonList 匹配
  353. this.returnMethod = String(data.sendType);
  354. // 查找 returnMethodText
  355. const methodObj = this.returnMethodList.find(m => m.value === this.returnMethod);
  356. this.returnMethodText = methodObj ? methodObj.label : '';
  357. this.description = data.description || '';
  358. // 回填图片
  359. if (data.fileUrlList) {
  360. this.fileList = data.fileUrlList;
  361. }
  362. // 初始化选中状态
  363. this.orderInfo.detailVoList.forEach(item => {
  364. this.$set(item, 'checked', true);
  365. // 修改模式下,默认显示上次申请的数量
  366. this.$set(item, 'refundNum', item.refundNum || 1);
  367. });
  368. this.address = {
  369. id: data.sendAddressId,
  370. name: data.sendName,
  371. mobile: data.sendMobile,
  372. fullAddress: data.sendAddress,
  373. }
  374. }
  375. });
  376. },
  377. loadOrderDetail() {
  378. this.$u.api.getShopOrderDetailAjax({ orderId: this.orderId }).then(res => {
  379. if (res.code === 200) {
  380. this.orderInfo = res.data;
  381. // 初始化商品选中状态和退款数量
  382. this.orderInfo.detailVoList.forEach(item => {
  383. this.$set(item, 'checked', true);
  384. this.$set(item, 'refundNum', item.num);
  385. });
  386. // 根据状态默认选中类型
  387. if (this.orderInfo.status == '3') { // 待收货
  388. // 默认退货退款
  389. this.refundType = '1';
  390. this.refundTypeText = '退货退款';
  391. this.shopStatus = '1';
  392. this.shopStatusText = '未收到货';
  393. } else {
  394. this.refundType = '1';
  395. this.refundTypeText = '退货退款';
  396. this.shopStatus = '2';
  397. this.shopStatusText = '已收到货';
  398. }
  399. // 默认使用订单收货地址作为取件地址
  400. this.address = {
  401. id: this.orderInfo.receiverAddressId,
  402. name: this.orderInfo.receiverName,
  403. mobile: this.orderInfo.receiverMobile,
  404. fullAddress: this.orderInfo.receiverAddress,
  405. };
  406. }
  407. });
  408. },
  409. getRefundReasons() {
  410. uni.$u.http.get("/token/common/getDictOptions?type=shop_order_complaints_options").then(res => {
  411. if (res.code === 200) {
  412. this.reasonList = res.data.map(item => ({
  413. value: item.dictValue || item.dictLabel,
  414. label: item.dictLabel
  415. }));
  416. }
  417. });
  418. },
  419. toggleCheck(item) {
  420. item.checked = !item.checked;
  421. // 当勾选状态变化时,重置自定义退款金额,让系统重新计算
  422. this.customRefundMoney = '';
  423. },
  424. calculateRefundMoney() {
  425. // 当商品数量变化时,重置自定义退款金额,让系统重新计算
  426. this.customRefundMoney = '';
  427. },
  428. confirmType(e) {
  429. this.refundType = e[0].value;
  430. this.refundTypeText = e[0].label;
  431. // 切换类型时重置一些状态
  432. if (this.refundType === '2') { // 仅退款
  433. this.returnMethod = '';
  434. this.returnMethodText = '';
  435. } else {
  436. if (!this.returnMethod) {
  437. this.returnMethod = '3';
  438. this.returnMethodText = '自行寄回';
  439. }
  440. }
  441. // 切换退款类型时,重置自定义退款金额
  442. this.customRefundMoney = '';
  443. },
  444. confirmReason(e) {
  445. this.refundReason = e[0].value;
  446. this.refundReasonText = e[0].label;
  447. },
  448. confirmStatus(e) {
  449. this.shopStatus = e[0].value;
  450. this.shopStatusText = e[0].label;
  451. },
  452. confirmReturnMethod(e) {
  453. this.returnMethod = e[0].value;
  454. this.returnMethodText = e[0].label;
  455. },
  456. chooseAddress() {
  457. uni.navigateTo({
  458. url: `/pages-mine/pages/address/list?id=${this.address.id || ''}&isSelect=1`
  459. });
  460. },
  461. validateRefundMoney() {
  462. if (!this.customRefundMoney) {
  463. this.customRefundMoney = this.maxRefundMoney;
  464. return;
  465. }
  466. const refund = Number(this.customRefundMoney);
  467. const max = Number(this.maxRefundMoney);
  468. if (refund > max) {
  469. this.$u.toast(`退款金额不能超过最大可退金额 ¥${max}`);
  470. this.customRefundMoney = max.toFixed(2);
  471. } else if (refund < 0) {
  472. this.$u.toast('退款金额不能为负数');
  473. this.customRefundMoney = '0.00';
  474. }
  475. },
  476. confirmRefundMoney() {
  477. this.validateRefundMoney();
  478. this.showRefundAmountPopup = false;
  479. },
  480. submit() {
  481. if (this.selectedGoods.length === 0) {
  482. this.$u.toast('请选择退款商品');
  483. return;
  484. }
  485. if (!this.refundReason) {
  486. this.$u.toast('请选择退款原因');
  487. return;
  488. }
  489. if (this.refundType === '1' && !this.returnMethod) {
  490. this.$u.toast('请选择退货方式');
  491. return;
  492. }
  493. if (this.refundType === '1' && this.returnMethod === '1' && !this.address.name) {
  494. this.$u.toast('请选择取件地址');
  495. return;
  496. }
  497. // 处理图片
  498. let files = this.fileList;
  499. const params = {
  500. orderId: this.orderId,
  501. refundDetailList: this.selectedGoods.map(item => ({
  502. detailOrderId: item.detailOrderId || item.id,
  503. num: item.refundNum
  504. })),
  505. refundType: this.refundType,
  506. shopStatus: this.shopStatus,
  507. refundReason: this.refundReason,
  508. refundMoney: this.refundMoney,
  509. sendType: this.returnMethod,
  510. addressId: this.address.id || '',
  511. description: this.description,
  512. fileUrlList: files
  513. };
  514. // 附加地址信息(如果是上门取件)
  515. if (this.refundType === '1' && this.returnMethod === '1') {
  516. // 这里的参数结构需根据后端实际要求调整,这里假设后端接收 address 对象
  517. params.address = this.address;
  518. }
  519. const api = this.isModify ? this.$u.api.refundApplyModifyAjax : this.$u.api.applyRefundAjax;
  520. // 修改模式下可能需要传入 refundOrderId (虽然截图只有 orderId, 但为了保险起见或者后端根据 orderId 找)
  521. // 截图显示 Request body 包含 orderId 和 refundDetailList 等。
  522. // 如果是修改,通常 orderId 指的是原订单ID。后端可能通过 orderId 找到对应的 active refund application 或者需要 refundOrderId。
  523. // 根据截图,只有 body,没有 path param。body 里有 orderId。
  524. // 如果是修改,我们可能需要额外传 refundOrderId 吗? 截图里没有显示 refundOrderId 字段。
  525. // 假设后端通过 orderId 识别当前正在进行的退款申请进行修改,或者复用 applyRefund 的逻辑只是接口不同。
  526. api(params).then(res => {
  527. if (res.code === 200) {
  528. this.$u.toast(this.isModify ? '修改成功' : '提交成功');
  529. setTimeout(() => {
  530. uni.navigateBack();
  531. }, 1500);
  532. } else {
  533. this.$u.toast(res.msg || (this.isModify ? '修改失败' : '提交失败'));
  534. }
  535. });
  536. }
  537. }
  538. }
  539. </script>
  540. <style lang="scss" scoped>
  541. .apply-refund-page {
  542. min-height: 100vh;
  543. background-color: #f5f5f5;
  544. padding: 20rpx;
  545. padding-bottom: 140rpx; // Space for fixed footer
  546. }
  547. .card {
  548. background-color: #fff;
  549. border-radius: 16rpx;
  550. margin-bottom: 20rpx;
  551. padding: 30rpx;
  552. overflow: hidden;
  553. &.no-padding {
  554. padding: 0;
  555. }
  556. .type-section {
  557. margin-bottom: 30rpx;
  558. .section-title {
  559. font-size: 30rpx;
  560. font-weight: bold;
  561. color: #333;
  562. margin-bottom: 20rpx;
  563. }
  564. .type-btn-group {
  565. display: flex;
  566. .type-btn {
  567. flex: 1;
  568. height: 64rpx;
  569. display: flex;
  570. align-items: center;
  571. justify-content: center;
  572. border: 2rpx solid #e5e5e5;
  573. border-radius: 8rpx;
  574. margin-right: 20rpx;
  575. font-size: 28rpx;
  576. color: #333;
  577. transition: all 0.3s;
  578. &:last-child {
  579. margin-right: 0;
  580. }
  581. &.active {
  582. border-color: #38C148;
  583. color: #38C148;
  584. background-color: rgba(56, 193, 72, 0.05);
  585. font-weight: bold;
  586. }
  587. }
  588. }
  589. }
  590. .card-header {
  591. padding: 10rpx 0 20rpx;
  592. border-bottom: 1rpx solid #f5f5f5;
  593. margin-bottom: 20rpx;
  594. .card-title {
  595. font-size: 30rpx;
  596. font-weight: bold;
  597. color: #333;
  598. }
  599. }
  600. .card-title {
  601. // For simple title without header line
  602. font-size: 30rpx;
  603. font-weight: bold;
  604. color: #333;
  605. margin-bottom: 10rpx;
  606. }
  607. }
  608. .goods-list {
  609. .goods-item {
  610. display: flex;
  611. align-items: center;
  612. margin-bottom: 30rpx;
  613. &:last-child {
  614. margin-bottom: 0;
  615. }
  616. .checkbox-box {
  617. margin-right: 20rpx;
  618. }
  619. .goods-cover {
  620. width: 140rpx;
  621. height: 150rpx;
  622. border-radius: 8rpx;
  623. margin-right: 20rpx;
  624. }
  625. .goods-info {
  626. flex: 1;
  627. height: 160rpx;
  628. display: flex;
  629. flex-direction: column;
  630. justify-content: space-between;
  631. .goods-name {
  632. font-size: 28rpx;
  633. color: #333;
  634. line-height: 1.4;
  635. }
  636. .goods-sku {
  637. font-size: 24rpx;
  638. color: #999;
  639. }
  640. .price-box {
  641. display: flex;
  642. justify-content: space-between;
  643. align-items: center;
  644. .price {
  645. font-size: 30rpx;
  646. color: #333;
  647. font-weight: 500;
  648. }
  649. }
  650. }
  651. }
  652. }
  653. .refund-money-box {
  654. display: flex;
  655. align-items: center;
  656. .money {
  657. font-size: 32rpx;
  658. color: #38C148; // Theme color
  659. font-weight: bold;
  660. }
  661. .edit-tag {
  662. display: flex;
  663. align-items: center;
  664. background-color: #f0f0f0;
  665. padding: 4rpx 12rpx;
  666. border-radius: 20rpx;
  667. margin-left: 16rpx;
  668. text {
  669. font-size: 22rpx;
  670. color: #666;
  671. margin-left: 4rpx;
  672. }
  673. }
  674. }
  675. .address-box {
  676. flex: 1;
  677. display: flex;
  678. align-items: center;
  679. justify-content: flex-end;
  680. text-align: right;
  681. color: #333;
  682. font-size: 28rpx;
  683. .addr-detail {
  684. font-size: 24rpx;
  685. color: #999;
  686. max-width: 300rpx;
  687. }
  688. }
  689. .footer-bar {
  690. position: fixed;
  691. bottom: 0;
  692. left: 0;
  693. right: 0;
  694. height: 200rpx;
  695. background-color: #fff;
  696. display: flex;
  697. align-items: center;
  698. padding: 0 30rpx;
  699. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  700. z-index: 100;
  701. padding-bottom: constant(safe-area-inset-bottom);
  702. padding-bottom: env(safe-area-inset-bottom);
  703. .footer-left {
  704. flex: 1;
  705. display: flex;
  706. align-items: center;
  707. .label {
  708. font-size: 26rpx;
  709. color: #333;
  710. }
  711. .amount {
  712. font-size: 36rpx;
  713. color: #38C148;
  714. font-weight: bold;
  715. margin: 0 10rpx;
  716. }
  717. .detail-link {
  718. font-size: 24rpx;
  719. color: #999;
  720. text-decoration: underline;
  721. }
  722. }
  723. .footer-right {
  724. width: 240rpx;
  725. }
  726. }
  727. .popup-container {
  728. padding: 30rpx;
  729. background-color: #fff;
  730. &.full-height {
  731. height: 100%;
  732. display: flex;
  733. flex-direction: column;
  734. }
  735. .popup-header {
  736. display: flex;
  737. justify-content: space-between;
  738. align-items: center;
  739. margin-bottom: 40rpx;
  740. flex-shrink: 0;
  741. .title {
  742. font-size: 32rpx;
  743. font-weight: bold;
  744. color: #333;
  745. }
  746. }
  747. .popup-content {
  748. margin-bottom: 40rpx;
  749. flex: 1;
  750. overflow-y: auto;
  751. .detail-item {
  752. margin-bottom: 30rpx;
  753. .item-row {
  754. display: flex;
  755. align-items: center;
  756. margin-bottom: 10rpx;
  757. .item-label {
  758. flex: 1;
  759. margin-left: 20rpx;
  760. font-size: 30rpx;
  761. color: #333;
  762. }
  763. .item-value {
  764. font-size: 30rpx;
  765. color: #333;
  766. font-weight: 500;
  767. }
  768. }
  769. .sub-list {
  770. padding-left: 60rpx;
  771. .sub-item {
  772. display: flex;
  773. justify-content: space-between;
  774. margin-top: 10rpx;
  775. font-size: 26rpx;
  776. color: #999;
  777. }
  778. }
  779. .money-input {
  780. width: 200rpx;
  781. text-align: right;
  782. font-size: 30rpx;
  783. color: #333;
  784. font-weight: 500;
  785. }
  786. .max-money-hint {
  787. margin-top: 10rpx;
  788. padding-left: 60rpx;
  789. font-size: 24rpx;
  790. color: #999;
  791. }
  792. }
  793. .upload-textarea-box {
  794. background-color: #f9f9f9;
  795. padding: 20rpx;
  796. border-radius: 12rpx;
  797. .counter {
  798. text-align: right;
  799. font-size: 24rpx;
  800. color: #999;
  801. margin: 10rpx 0 0;
  802. }
  803. }
  804. .upload-area {
  805. margin-top: 20rpx;
  806. }
  807. }
  808. .popup-footer {
  809. margin-top: 20rpx;
  810. flex-shrink: 0;
  811. &.safe-area-bottom {
  812. padding-bottom: constant(safe-area-inset-bottom);
  813. padding-bottom: env(safe-area-inset-bottom);
  814. }
  815. }
  816. }
  817. .address-section {
  818. padding: 26rpx 32rpx;
  819. }
  820. .address-text {
  821. color: #333333;
  822. font-family: PingFang SC;
  823. font-weight: 400;
  824. }
  825. .address-label {
  826. font-size: 28rpx;
  827. color: #909399;
  828. margin-right: 20rpx;
  829. width: 140rpx;
  830. }
  831. .u-required {
  832. color: #ff0000;
  833. margin-left: 8rpx;
  834. }
  835. </style>