refund.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. <template>
  2. <view class="refund-page">
  3. <!-- 跨天售后提示:接口预检 / 本地兜底 / 订单 shAddPay -->
  4. <view class="warning-tip" v-if="showNextDayTip">
  5. <view class="warning-icon">⚠</view>
  6. <text class="warning-text" v-if="shAddPayReason == 1">已过可售后时间,将按跨天售后办理</text>
  7. <text class="warning-text" v-else-if="orderCleared || Number(orderInfo.clearId) > 0 || (Number(orderInfo.debtPrice) > 0 && Number(orderInfo.remainDebtPrice) <= 0)">
  8. 订单已结账,将按跨天售后办理
  9. </text>
  10. <text class="warning-text" v-else-if="shAddPay == 1 && Number(orderInfo.remainDebtPrice) > 0">
  11. 订单已部分结账或售后超额,将按跨天售后办理
  12. </text>
  13. <text class="warning-text" v-else>非付款日,将按跨天售后办理</text>
  14. </view>
  15. <!-- 退款方式选择卡片 -->
  16. <view class="card-section">
  17. <view class="section-title">
  18. <text class="title-text">退款方式</text>
  19. </view>
  20. <view class="refund-type-buttons">
  21. <button
  22. class="admin-button-com big"
  23. :class="refundType==1?'blue':'default'"
  24. @tap="refundType=1">
  25. 退货并退款
  26. </button>
  27. <button
  28. class="admin-button-com big"
  29. :class="refundType==2?'blue':'default'"
  30. @tap="refundType=2">
  31. 只退款
  32. </button>
  33. </view>
  34. </view>
  35. <!-- 商品选择卡片 -->
  36. <view class="card-section" v-if="refundType==1">
  37. <view class="section-title">
  38. <text class="title-text">退货商品</text>
  39. </view>
  40. <view class="product-list-compact">
  41. <template v-if="!$util.isEmpty(goodsInfoList)">
  42. <view class="product-row" v-for="(res,i) in goodsInfoList" :key="i">
  43. <view class="product-basic-info">
  44. <text class="product-name-compact">{{res.name}}</text>
  45. <view class="product-meta">
  46. <text class="stock-info">{{parseFloat(res.num)}}份 X ¥{{parseFloat(res.unitPrice)}}</text>
  47. <text class="available-info">可退 {{Number(res.num)-Number(res.refundNum)}}份</text>
  48. </view>
  49. </view>
  50. <view class="refund-control">
  51. <view class="refund-input-group">
  52. <view class="input-item">
  53. <text class="input-label">数量</text>
  54. <input
  55. class="refund-input-compact"
  56. type="number"
  57. placeholder="0"
  58. v-model="res.refundCount"
  59. placeholder-style="color:#ccc"
  60. @focus="clearRefundCount(res)"
  61. @input="onRefundCountInput" />
  62. <text class="unit-label">份</text>
  63. </view>
  64. </view>
  65. </view>
  66. </view>
  67. </template>
  68. <template v-if="!$util.isEmpty(itemInfoList)">
  69. <view class="product-row" v-for="(res,i) in itemInfoList" :key="i">
  70. <view class="product-basic-info">
  71. <text class="product-name-compact">{{res.name}}</text>
  72. <view class="product-meta">
  73. <text class="stock-info">{{parseFloat(res.num)}}{{res.unitName}} X ¥{{parseFloat(res.unitPrice)}}</text>
  74. <text class="available-info">可退 {{Number(res.num)-Number(res.refundNum)}}{{res.unitName}}</text>
  75. </view>
  76. </view>
  77. <view class="refund-control">
  78. <view class="refund-input-group">
  79. <view class="input-item">
  80. <text class="input-label">数量</text>
  81. <input
  82. class="refund-input-compact"
  83. type="number"
  84. placeholder="0"
  85. v-model="res.refundCount"
  86. placeholder-style="color:#ccc"
  87. @focus="clearRefundCount(res)"
  88. @input="onRefundCountInput" />
  89. <text class="unit-label">{{res.unitName}}</text>
  90. </view>
  91. </view>
  92. </view>
  93. </view>
  94. </template>
  95. </view>
  96. </view>
  97. <!-- 金额信息卡片 -->
  98. <view class="card-section">
  99. <view class="section-title">
  100. <text class="title-text">金额信息</text>
  101. </view>
  102. <view class="amount-info">
  103. <view class="amount-row">
  104. <text class="amount-label">订单金额</text>
  105. <view class="amount-value">
  106. <text class="price-text">¥{{parseFloat(orderInfo.orderPrice)||0}}</text>
  107. </view>
  108. </view>
  109. <view class="amount-row" v-if="Number(orderInfo.tkPrice)>0">
  110. <text class="amount-label">已退金额</text>
  111. <view class="amount-value">
  112. <text class="refunded-text">¥{{orderInfo.tkPrice ? parseFloat(orderInfo.tkPrice) : 0}}</text>
  113. </view>
  114. </view>
  115. <view class="amount-row">
  116. <text class="amount-label">可退金额</text>
  117. <text class="amount-value available-amount">¥{{coundRefundPrice}}</text>
  118. </view>
  119. <view class="amount-row input-row">
  120. <text class="amount-label required">退款金额</text>
  121. <view class="amount-input-wrapper">
  122. <text class="currency-symbol">¥</text>
  123. <input
  124. class="amount-input"
  125. type="digit"
  126. v-model="refundMoney"
  127. placeholder="请输入"
  128. placeholder-style="color:#999"
  129. @focus="clearRefundMoney">
  130. </view>
  131. </view>
  132. <view class="amount-row" v-if="orderInfo.hbId && orderInfo.hbId != '0'">
  133. <view style="width: 100%;">
  134. <view style="display: flex;justify-content: space-between;align-items: center;">
  135. <text class="amount-label">红包</text>
  136. <view class="amount-value">
  137. <button
  138. class="admin-button-com middle"
  139. :class="refundHb==0?'blue':'default'"
  140. @tap="setRefundHb(0)" style="margin-right: 28upx;">
  141. 不退
  142. </button>
  143. <button
  144. class="admin-button-com middle"
  145. :class="refundHb==1?'blue':'default'"
  146. @tap="setRefundHb(1)" >
  147. 退回
  148. </button>
  149. </view>
  150. </view>
  151. <view style="color: red;font-size: 24upx;text-align: right;margin-top: 10upx;" v-if="!canRefundBackHb">
  152. 订单全额退款时,红包才能选择退回
  153. </view>
  154. </view>
  155. </view>
  156. </view>
  157. </view>
  158. <!-- 备注卡片 -->
  159. <view class="card-section">
  160. <view class="section-title">
  161. <text class="title-text">备注</text>
  162. </view>
  163. <view class="remark-container">
  164. <textarea
  165. class="remark-textarea"
  166. v-model="remark"
  167. placeholder="选填"
  168. placeholder-style="color:#999"
  169. @focus="clearRemark" />
  170. </view>
  171. </view>
  172. <!-- 底部操作按钮 -->
  173. <view class="bottom-actions">
  174. <button class="action-btn cancel-btn" @tap="cancelRefund">取消</button>
  175. <button class="action-btn confirm-btn" @tap="askRefund">退款</button>
  176. </view>
  177. <!-- 当天售后确认弹框 -->
  178. <ModalModule
  179. :show="showRefundModal"
  180. :custom="true"
  181. :button="[]"
  182. width="90%"
  183. @click="handleRefundModalClick">
  184. <view class="refund-confirm-modal">
  185. <view class="modal-header">
  186. <text class="modal-title">提示</text>
  187. </view>
  188. <view class="modal-content">
  189. <view class="amount-display">
  190. <text class="amount-label">退款金额</text>
  191. <text class="amount-value">¥{{refundMoney}}</text>
  192. </view>
  193. <view class="confirm-text">
  194. <text>确认要退款吗?</text>
  195. </view>
  196. </view>
  197. <view class="modal-actions">
  198. <button class="modal-btn cancel-modal-btn" @tap="cancelRefundModal">取消</button>
  199. <button class="modal-btn confirm-modal-btn" @tap="executeRefund">确认退款</button>
  200. </view>
  201. </view>
  202. </ModalModule>
  203. <!-- 跨天售后确认(对齐 ghs:写 sameDay=0,资金原路) -->
  204. <forward-options-popup
  205. :show="showForwardPop"
  206. :order-cleared="orderCleared"
  207. @cancel="showForwardPop = false"
  208. @confirm="onForwardConfirm"
  209. />
  210. </view>
  211. </template>
  212. <script>
  213. /**
  214. * 花店订单售后页:选择退货商品、填写退款金额并提交。
  215. * 退货单价直接取订单原单价,只需填数量。
  216. * 跨天售后:预检 + 确认弹窗后提交 sameDay=0;成功进 forwardResult。
  217. */
  218. import { getDetail,refund } from "@/api/order/index";
  219. import { checkNextDayEligible } from "@/api/refund/index";
  220. import ModalModule from "@/components/plugin/modal";
  221. import ForwardOptionsPopup from "@/components/forward-options-popup";
  222. export default {
  223. name: "orderDetail",
  224. components: { ModalModule, ForwardOptionsPopup },
  225. data() {
  226. return {
  227. goodsInfoList:[],
  228. itemInfoList:[],
  229. refundMoney:0,
  230. refundHb:0, //红包退回选择 0表示不退,1表示退回
  231. refundType:1,
  232. remark:'',
  233. orderInfo:{},
  234. coundRefundPrice:0,
  235. showRefundModal: false,
  236. showForwardPop: false,
  237. nextDayEligible: false,
  238. orderCleared: false,
  239. _pendingRefundParams: null,
  240. shAddPay:0,//0正常当天售后,1跨天售后(订单详情兜底)
  241. shAddPayReason:0,//0欠款已结清或部分结清,1可售后时间到了
  242. };
  243. },
  244. computed:{
  245. /** 顶部隔天提示:接口预检 / 本地兜底 / 订单 shAddPay */
  246. showNextDayTip() {
  247. return this.nextDayEligible || this.shAddPay == 1 || this.isMustNextDayOrder()
  248. },
  249. canRefundBackHb(){
  250. // 订单全额退款时(即 refundMoney == coundRefundPrice),红包才能选择退回
  251. if (this.refundMoney === '' || this.refundMoney === null || this.refundMoney === undefined) return false
  252. const refundMoney = Number(this.refundMoney)
  253. const couldRefund = Number(this.coundRefundPrice)
  254. if (Number.isNaN(refundMoney) || Number.isNaN(couldRefund)) return false
  255. // 金额比较按 2 位小数对齐,避免浮点误差
  256. return refundMoney.toFixed(2) === couldRefund.toFixed(2)
  257. },
  258. /**
  259. * 按退货数量 × 商品原单价汇总金额,并回填退款金额。
  260. * 单价不再手填,避免漏填导致退款金额为 0。
  261. */
  262. refundPrice(){
  263. let price = 0
  264. if(!this.$util.isEmpty(this.goodsInfoList)){
  265. for (const item of this.goodsInfoList) {
  266. if(Number(item.refundCount)>0){
  267. let currentPrice = Number(item.refundCount)*Number(item.unitPrice)
  268. price = Number(price) + Number(currentPrice)
  269. }
  270. }
  271. }
  272. if(!this.$util.isEmpty(this.itemInfoList)){
  273. for (const item of this.itemInfoList) {
  274. if(Number(item.refundCount)>0){
  275. let currentPrice = Number(item.refundCount)*Number(item.unitPrice)
  276. price = Number(price) + Number(currentPrice)
  277. }
  278. }
  279. }
  280. this.refundMoney = parseFloat(price.toFixed(2))
  281. return price
  282. }
  283. },
  284. watch: {
  285. refundMoney() {
  286. // 如果已经选了“退回”,但修改退款金额后不满足全额退款条件,则自动回退为“不退”
  287. if (this.refundHb == 1 && !this.canRefundBackHb) {
  288. this.refundHb = 0
  289. }
  290. }
  291. },
  292. methods: {
  293. setRefundHb(val){
  294. if(val == 1){
  295. if(!this.canRefundBackHb){
  296. this.$msg('订单全额退款时,红包才能选择退回')
  297. return
  298. }
  299. }
  300. this.refundHb = val
  301. },
  302. clearRefundCount(item) {
  303. this.$set(item, 'refundCount', '');
  304. this.onRefundCountInput()
  305. },
  306. /**
  307. * 退货数量变化后读取 refundPrice,按原单价回填退款金额。
  308. * 计算属性默认懒求值,不主动读取就不会更新退款金额。
  309. */
  310. onRefundCountInput() {
  311. this.$nextTick(() => {
  312. void this.refundPrice
  313. })
  314. },
  315. clearRefundMoney() {
  316. this.refundMoney = '';
  317. },
  318. clearRemark() {
  319. this.remark = '';
  320. },
  321. cancelRefund(){
  322. uni.navigateBack({delta: 1});
  323. },
  324. askRefund(){
  325. if(this.orderInfo.payWay == 3 && Number(this.orderInfo.remainDebtPrice)>0 && Number(this.refundMoney) > Number(this.orderInfo.remainDebtPrice)){
  326. if(this.refundType==2){
  327. uni.showToast({title:'跨天售后请选择商品',icon:'none'})
  328. return false
  329. }
  330. // 售后金额超过剩余欠款:按跨天售后办理
  331. this.$util.confirmModal({content:'售后金额超过剩余欠款,将按跨天售后办理'},() => {
  332. this.shAddPay = 1
  333. this.confirmRefund()
  334. })
  335. }else{
  336. this.confirmRefund()
  337. }
  338. },
  339. confirmRefund(){
  340. let hasError = false
  341. let selectProduct = []
  342. if(!this.$util.isEmpty(this.goodsInfoList)){
  343. this.goodsInfoList.forEach(ele=>{
  344. let currentRefundCont = ele.refundCount ? Number(ele.refundCount) :0
  345. let currentNum = ele.num ? Number(ele.num) : 0
  346. let hasRefundNum = ele.refundNum ? Number(ele.refundNum) : 0
  347. let couldRefundNum = Number(currentNum) - Number(hasRefundNum)
  348. if(Number(currentRefundCont) > Number(couldRefundNum)){
  349. uni.showToast({title:ele.name+'超过可退数量',icon:'none'})
  350. hasError = true;
  351. return false
  352. }
  353. if(Number(ele.refundCount)>0){
  354. selectProduct.push({"productId":ele.goodsId,"num":ele.refundCount,"unitType":ele.unitType,unitName:ele.unitName,unitPrice:ele.unitPrice,property:0})
  355. }
  356. })
  357. }
  358. if(!this.$util.isEmpty(this.itemInfoList)){
  359. this.itemInfoList.forEach(ele=>{
  360. let currentRefundCont = ele.refundCount ? Number(ele.refundCount) :0
  361. let currentNum = ele.num ? Number(ele.num) : 0
  362. let hasRefundNum = ele.refundNum ? Number(ele.refundNum) : 0
  363. let couldRefundNum = Number(currentNum) - Number(hasRefundNum)
  364. if(Number(currentRefundCont) > Number(couldRefundNum)){
  365. uni.showToast({title:ele.name+'超过可退数量',icon:'none'})
  366. hasError = true;
  367. return false
  368. }
  369. if(Number(ele.refundCount)>0){
  370. selectProduct.push({"productId":ele.itemId,"num":ele.refundCount,"unitType":ele.unitType,unitName:ele.unitName,unitPrice:ele.unitPrice,property:1})
  371. }
  372. })
  373. }
  374. if(hasError){
  375. return
  376. }
  377. if(this.$util.isEmpty(selectProduct) && this.refundType==1){
  378. uni.showToast({title:'请选择商品',icon:'none'})
  379. return;
  380. }
  381. if(Number(this.refundMoney)<=0){
  382. this.$msg('请填写退款金额')
  383. return false
  384. }
  385. const selectProductJson = JSON.stringify(selectProduct)
  386. this._pendingRefundParams = {
  387. product: selectProductJson,
  388. id: this.option.id,
  389. price: this.refundMoney,
  390. remark: this.remark,
  391. refundType: this.refundType,
  392. refundHb: this.refundHb,
  393. getOriginalItem: 1,
  394. version: 2
  395. }
  396. // 非当天 / 已结清 / 预检通过 → 跨天售后确认弹窗
  397. if (this.nextDayEligible || this.shAddPay == 1 || this.isMustNextDayOrder()) {
  398. if (!this.nextDayEligible && !this.orderCleared) {
  399. this.orderCleared = Number(this.orderInfo.clearId) > 0
  400. || (Number(this.orderInfo.debtPrice) > 0 && Number(this.orderInfo.remainDebtPrice) <= 0)
  401. }
  402. this.showForwardPop = true
  403. return
  404. }
  405. // 当天售后:原确认弹框
  406. this.showRefundModal = true
  407. },
  408. /** 是否有有效支付时间(与后端 hasValidPayTime 一致) */
  409. hasValidPayTime() {
  410. const payTime = (this.orderInfo && this.orderInfo.payTime) || ''
  411. return !!payTime && payTime !== '0000-00-00 00:00:00'
  412. },
  413. /**
  414. * 本地判断是否必须跨天售后:非当天支付 / 已结账单 / 挂账已结清
  415. */
  416. isMustNextDayOrder() {
  417. const info = this.orderInfo || {}
  418. if (!this.hasValidPayTime()) {
  419. return false
  420. }
  421. if (Number(info.clearId) > 0) {
  422. return true
  423. }
  424. const debtPrice = Number(info.debtPrice) || 0
  425. const remainDebt = Number(info.remainDebtPrice) || 0
  426. if (debtPrice > 0 && remainDebt === 0) {
  427. return true
  428. }
  429. const payDay = String(info.payTime).substr(0, 10)
  430. const now = new Date()
  431. const m = now.getMonth() + 1
  432. const d = now.getDate()
  433. const today = now.getFullYear() + '-' + (m < 10 ? '0' + m : m) + '-' + (d < 10 ? '0' + d : d)
  434. return payDay !== today
  435. },
  436. // 弹框相关方法
  437. handleRefundModalClick(e) {
  438. // 处理弹框点击事件
  439. },
  440. cancelRefundModal() {
  441. this.showRefundModal = false
  442. },
  443. /** 当天售后确认提交 */
  444. executeRefund() {
  445. this.showRefundModal = false
  446. this.doRefund(this._pendingRefundParams || {})
  447. },
  448. /**
  449. * 跨天售后确认:写 sameDay=0,资金按原单原路处理
  450. */
  451. onForwardConfirm() {
  452. this.showForwardPop = false
  453. const params = Object.assign({}, this._pendingRefundParams || {}, {
  454. sameDay: 0
  455. })
  456. this.doRefund(params)
  457. },
  458. /**
  459. * 提交售后退款;成功后统一进 forwardResult(对齐 ghs 端)
  460. * - 待选手选落地 / 已返充海报 / 已原路成功:均由 forwardResult 展示
  461. * - 不再按 payWay 跳转 onlineResult / balanceCp / offlineRemind / debtResult
  462. * - hd 无 ghs 的 refundSuccess(报损减库),原路终态也走 forwardResult 简版成功态
  463. */
  464. doRefund(refundParams){
  465. uni.showLoading({mask:true})
  466. refund(refundParams).then(res => {
  467. uni.hideLoading()
  468. if(res.code == 1){
  469. const data = res.data || {}
  470. // 待确认退回方式:当天/隔天线下等 hasReturn=0 且 couldReturn=1
  471. const needFund = Number(data.needFundAction) === 1
  472. || (Number(data.hasReturn) === 0
  473. && Number(data.returnBalance) === 0
  474. && Number(data.couldReturn) === 1)
  475. const payload = {
  476. amount: data.refundPrice || this.refundMoney,
  477. customId: data.customId || this.orderInfo.customId,
  478. customBalance: data.customBalance != null ? data.customBalance : '',
  479. customName: data.customName || this.orderInfo.customName || '',
  480. forwardSn: data.refundSn || data.orderSn || '',
  481. forwardId: data.id || 0,
  482. orderId: data.orderId || this.orderInfo.id || 0,
  483. payWay: data.payWay != null ? data.payWay : this.orderInfo.payWay,
  484. onlinePay: data.onlinePay != null ? data.onlinePay : this.orderInfo.onlinePay,
  485. hasReturn: data.hasReturn != null ? data.hasReturn : 0,
  486. couldReturn: data.couldReturn != null ? data.couldReturn : 1,
  487. returnBalance: data.returnBalance != null ? data.returnBalance : 0,
  488. needFundAction: needFund ? 1 : 0
  489. }
  490. uni.setStorageSync('forwardResultPayload', payload)
  491. this.$util.pageTo({
  492. url: '/admin/billing/forwardResult',
  493. type: 2,
  494. query: Object.assign({}, payload, {
  495. customName: encodeURIComponent(payload.customName || '')
  496. })
  497. })
  498. }
  499. })
  500. },
  501. async init() {
  502. const res = await getDetail({ id: this.option.id})
  503. this.orderInfo = res.data
  504. this.shAddPay = this.orderInfo.shAddPay?this.orderInfo.shAddPay:0
  505. this.shAddPayReason = this.orderInfo.shAddPayReason?this.orderInfo.shAddPayReason:0
  506. this.coundRefundPrice = Number(res.data.orderPrice) - Number(res.data.tkPrice || 0) - Number(res.data.nextDayTkPrice || 0)
  507. this.coundRefundPrice = this.coundRefundPrice.toFixed(2)
  508. this.coundRefundPrice = parseFloat(this.coundRefundPrice)
  509. this.goodsInfoList = res.data.goodsInfoList.map(ele=>{
  510. return {...ele,refundCount:null}
  511. })
  512. this.itemInfoList = res.data.itemInfoList.map(ele=>{
  513. return {...ele,refundCount:null}
  514. })
  515. // 预检是否可走跨天售后(对齐 ghs)
  516. checkNextDayEligible({ orderId: this.option.id }).then(r => {
  517. if (r.code == 1 && r.data && r.data.ok) {
  518. this.nextDayEligible = true
  519. this.orderCleared = Number(r.data.orderCleared) === 1
  520. } else {
  521. this.nextDayEligible = false
  522. if (!this.orderCleared) {
  523. this.orderCleared = Number(this.orderInfo.clearId) > 0
  524. }
  525. }
  526. }).catch(() => {
  527. this.nextDayEligible = false
  528. })
  529. }
  530. }
  531. };
  532. </script>
  533. <style lang="scss" scoped>
  534. .refund-page {
  535. min-height: 100vh;
  536. background: #f5f6f8;
  537. padding: 20upx 0 200upx;
  538. }
  539. // 红色提示样式
  540. .warning-tip {
  541. background: #fff2f0;
  542. border: 1upx solid #ffccc7;
  543. border-radius: 8upx;
  544. margin: 0upx 20upx 20upx 20upx;
  545. padding: 20upx;
  546. display: flex;
  547. align-items: center;
  548. justify-content: center;
  549. .warning-icon {
  550. font-size: 40upx;
  551. margin-right: 15upx;
  552. color: #ff4757;
  553. font-weight: bold;
  554. line-height: 1;
  555. }
  556. .warning-text {
  557. font-size: 34upx;
  558. color: #ff4757;
  559. font-weight:bold;
  560. text-align: center;
  561. }
  562. }
  563. // 卡片样式
  564. .card-section {
  565. background: #fff;
  566. margin: 0 20upx 20upx;
  567. border-radius: 16upx;
  568. box-shadow: 0 2upx 12upx rgba(0, 0, 0, 0.08);
  569. overflow: hidden;
  570. }
  571. // 节标题
  572. .section-title {
  573. padding: 30upx 30upx 20upx;
  574. border-bottom: 1upx solid #f0f0f0;
  575. display: flex;
  576. align-items: center;
  577. .title-text {
  578. font-size: 32upx;
  579. font-weight:600;
  580. color: #333;
  581. }
  582. }
  583. // 退款方式按钮
  584. .refund-type-buttons {
  585. padding: 30upx 30upx 20upx;
  586. display: flex;
  587. gap: 30upx;
  588. .admin-button-com {
  589. flex: 1;
  590. border-radius: 12upx;
  591. font-weight: 500;
  592. transition: all 0.3s ease;
  593. }
  594. }
  595. // 紧凑商品列表
  596. .product-list-compact {
  597. margin-top:15upx;
  598. padding: 0 20upx 20upx;
  599. }
  600. .product-row {
  601. display: flex;
  602. align-items: center;
  603. justify-content: space-between;
  604. padding: 20upx;
  605. margin-bottom: 8upx;
  606. background: #fafbfc;
  607. border-radius: 8upx;
  608. border: 1upx solid #eef0f2;
  609. &:last-child {
  610. margin-bottom: 0;
  611. }
  612. }
  613. .product-basic-info {
  614. flex: 1;
  615. margin-right: 20upx;
  616. .product-name-compact {
  617. font-size: 32upx;
  618. font-weight: 600;
  619. color: #333;
  620. line-height: 1.3;
  621. display: block;
  622. margin-bottom: 8upx;
  623. overflow: hidden;
  624. text-overflow: ellipsis;
  625. white-space: nowrap;
  626. width: 380upx;
  627. }
  628. .product-meta {
  629. display: flex;
  630. gap: 12upx;
  631. flex-wrap: wrap;
  632. .stock-info {
  633. font-size: 28upx;
  634. color: #666;
  635. background: #f5f5f5;
  636. padding: 4upx 8upx;
  637. border-radius: 4upx;
  638. }
  639. .available-info {
  640. font-size: 28upx;
  641. color: #09c567;
  642. background: rgba(255, 107, 53, 0.1);
  643. padding: 4upx 8upx;
  644. border-radius: 4upx;
  645. }
  646. }
  647. }
  648. .refund-control {
  649. display: flex;
  650. align-items: center;
  651. gap: 8upx;
  652. .refund-input-group {
  653. display: flex;
  654. flex-direction: column;
  655. gap: 12upx;
  656. .input-item {
  657. display: flex;
  658. align-items: center;
  659. gap: 8upx;
  660. .input-label {
  661. font-size: 24upx;
  662. color: #666;
  663. min-width: 60upx;
  664. }
  665. }
  666. }
  667. .refund-input-compact {
  668. width: 100upx;
  669. height: 60upx;
  670. background: #fff;
  671. border: 1upx solid #ddd;
  672. border-radius: 6upx;
  673. text-align: center;
  674. font-size: 26upx;
  675. color: #333;
  676. font-weight: 500;
  677. &:focus {
  678. border-color: #09c567;
  679. }
  680. }
  681. .unit-label {
  682. font-size: 24upx;
  683. color: #666;
  684. min-width: 40upx;
  685. }
  686. }
  687. // 金额信息
  688. .amount-info {
  689. padding: 30upx;
  690. }
  691. .amount-row {
  692. display: flex;
  693. justify-content: space-between;
  694. align-items: center;
  695. padding: 20upx 0;
  696. border-bottom: 1upx solid #f0f0f0;
  697. &:last-child {
  698. border-bottom: none;
  699. }
  700. &.input-row {
  701. align-items: flex-start;
  702. padding-top: 30upx;
  703. }
  704. .amount-label {
  705. font-size: 28upx;
  706. color: #666;
  707. &.required::after {
  708. content: "*";
  709. color: #ff4757;
  710. margin-left: 5upx;
  711. }
  712. }
  713. .amount-value {
  714. display: flex;
  715. align-items: center;
  716. .price-text {
  717. font-size: 32upx;
  718. font-weight: 600;
  719. color: #333;
  720. }
  721. .refunded-text {
  722. font-size: 32upx;
  723. font-weight: 600;
  724. color: #666;
  725. }
  726. &.available-amount {
  727. font-size: 32upx;
  728. font-weight: 600;
  729. color: #09c567;
  730. }
  731. &.refund-amount {
  732. font-size: 32upx;
  733. font-weight: 600;
  734. color: #c59b0f;
  735. }
  736. }
  737. }
  738. .amount-input-wrapper {
  739. display: flex;
  740. align-items: center;
  741. background: #fafbfc;
  742. border: 1upx solid #ddd;
  743. border-radius: 8upx;
  744. padding: 0 20upx;
  745. width: 190upx;
  746. .currency-symbol {
  747. font-size: 28upx;
  748. color: #666;
  749. margin-right: 10upx;
  750. }
  751. .amount-input {
  752. flex: 1;
  753. height: 80upx;
  754. font-size: 28upx;
  755. color: #333;
  756. font-weight: 500;
  757. border: none;
  758. background: transparent;
  759. width: 115upx;
  760. }
  761. }
  762. // 备注输入框
  763. .remark-container {
  764. padding: 20upx 30upx 30upx;
  765. }
  766. .remark-textarea {
  767. width: 100%;
  768. height: 120upx;
  769. background: #fafbfc;
  770. border: 1upx solid #ddd;
  771. border-radius: 8upx;
  772. padding: 15upx;
  773. font-size: 28upx;
  774. color: #333;
  775. line-height: 1.2;
  776. box-sizing: border-box;
  777. resize: none;
  778. }
  779. // 底部操作按钮
  780. .bottom-actions {
  781. z-index:39;
  782. position: fixed;
  783. bottom: 0;
  784. left: 0;
  785. right: 0;
  786. background: #fff;
  787. padding: 30upx;
  788. border-top: 1upx solid #eee;
  789. display: flex;
  790. gap: 30upx;
  791. box-shadow: 0 -4upx 12upx rgba(0, 0, 0, 0.1);
  792. }
  793. .action-btn {
  794. flex: 1;
  795. height: 88upx;
  796. border-radius: 12upx;
  797. font-size: 32upx;
  798. font-weight: 600;
  799. border: none;
  800. transition: all 0.3s ease;
  801. &.cancel-btn {
  802. background: #f8f9fa;
  803. color: #666;
  804. border: 1upx solid #ddd;
  805. &:active {
  806. background: #e9ecef;
  807. }
  808. }
  809. &.confirm-btn {
  810. background: linear-gradient(135deg, #09c567 0%, #00b894 100%);
  811. color: #fff;
  812. box-shadow: 0 4upx 12upx rgba(9, 197, 103, 0.3);
  813. &:active {
  814. transform: translateY(2upx);
  815. box-shadow: 0 2upx 8upx rgba(9, 197, 103, 0.3);
  816. }
  817. }
  818. }
  819. // 退款确认弹框样式
  820. .refund-confirm-modal {
  821. background: #fff;
  822. border-radius: 20upx;
  823. overflow: hidden;
  824. .modal-header {
  825. padding: 40upx 30upx 20upx;
  826. text-align: center;
  827. border-bottom: 1upx solid #f0f0f0;
  828. .modal-title {
  829. font-size: 36upx;
  830. font-weight: 600;
  831. color: #333;
  832. }
  833. }
  834. .modal-content {
  835. padding: 40upx 30upx;
  836. text-align: center;
  837. .amount-display {
  838. margin-bottom: 30upx;
  839. .amount-label {
  840. display: block;
  841. font-size: 28upx;
  842. color: #666;
  843. margin-bottom: 15upx;
  844. }
  845. .amount-value {
  846. display: block;
  847. font-size: 60upx;
  848. font-weight: 700;
  849. color: #ff4757;
  850. text-shadow: 0 2upx 4upx rgba(255, 71, 87, 0.2);
  851. }
  852. }
  853. .confirm-text {
  854. font-size: 30upx;
  855. color: #333;
  856. line-height: 1.5;
  857. }
  858. }
  859. .modal-actions {
  860. display: flex;
  861. gap: 20upx;
  862. padding: 0 30upx 40upx;
  863. .modal-btn {
  864. flex: 1;
  865. height: 80upx;
  866. border-radius: 12upx;
  867. font-size: 30upx;
  868. font-weight: 600;
  869. border: none;
  870. transition: all 0.3s ease;
  871. &.cancel-modal-btn {
  872. background: #f8f9fa;
  873. color: #666;
  874. border: 1upx solid #ddd;
  875. &:active {
  876. background: #e9ecef;
  877. }
  878. }
  879. &.confirm-modal-btn {
  880. background: linear-gradient(135deg, #ff4757 0%, #ff3742 100%);
  881. color: #fff;
  882. box-shadow: 0 4upx 12upx rgba(255, 71, 87, 0.3);
  883. &:active {
  884. transform: translateY(2upx);
  885. box-shadow: 0 2upx 8upx rgba(255, 71, 87, 0.3);
  886. }
  887. }
  888. }
  889. }
  890. }
  891. </style>