shish 4 дней назад
Родитель
Сommit
6df6026f86

+ 475 - 0
ghsApp/src/admin/billing/forwardResult.vue

@@ -0,0 +1,475 @@
+<!--
+  隔天冲销结果页 / 退款凭证海报
+  用途:供货商端隔天冲销(fundType=2 返充余额)成功后,用 canvas 绘制退款凭证并展示、保存相册;
+        支持 mode=repost 从详情页重新生成凭证。
+  谁用:admin/billing 冲销流程、售后转冲销、凭证详情重发入口。
+-->
+<template>
+  <view class="forward-result-page">
+    <!-- fundType=2:展示凭证海报 -->
+    <appResult v-if="Number(fundType) === 2" title="冲销成功">
+      <view v-if="posterLoading" class="status-text">凭证生成中...</view>
+      <view v-else-if="posterFail" class="fail-box">
+        <text class="status-text fail-text">凭证生成失败,请重试</text>
+        <button class="admin-button-com big blue action-btn" @click="generatePoster">重新生成凭证</button>
+      </view>
+      <view v-else-if="posterUrl" class="poster-preview">
+        <image
+          class="poster-img"
+          :src="posterUrl"
+          mode="widthFix"
+          show-menu-by-longpress
+        />
+        <text class="poster-tip">长按图片可转发或保存</text>
+      </view>
+      <button
+        v-if="posterUrl"
+        class="admin-button-com big blue action-btn"
+        @click="saveToAlbum"
+      >保存到相册</button>
+      <button
+        v-if="posterFail"
+        class="admin-button-com big action-btn"
+        @click="generatePoster"
+      >重新生成凭证</button>
+      <button class="admin-button-com big action-btn" @click="goBack">
+        {{ mode === 'repost' ? '返回' : '返回首页' }}
+      </button>
+    </appResult>
+
+    <!-- 非返余额:简单成功提示 -->
+    <appResult v-else :title="pageTitle">
+      <text class="simple-success">{{ simpleSuccessText }}</text>
+      <button class="admin-button-com big action-btn" @click="goBack">
+        {{ mode === 'repost' ? '返回' : '返回首页' }}
+      </button>
+    </appResult>
+
+    <!-- 离屏 canvas,用于绘制凭证海报 -->
+    <canvas
+      canvas-id="forwardPosterCanvas"
+      class="poster-canvas"
+      :style="{ width: canvasW + 'px', height: canvasH + 'px' }"
+    />
+  </view>
+</template>
+
+<script>
+import dayjs from 'dayjs'
+import appResult from '@/components/app-result'
+import { getGatheringMiniCode } from '@/api/custom'
+import { getUserDet } from '@/api/member'
+
+/** 本地缓存键:跳转本页前写入,onLoad 与 URL 参数合并 */
+const STORAGE_KEY = 'forwardResultPayload'
+
+export default {
+  name: 'forwardResult',
+  components: {
+    appResult
+  },
+  data() {
+    return {
+      fundType: 0,
+      amount: 0,
+      customId: 0,
+      customName: '',
+      customBalance: 0,
+      forwardSn: '',
+      forwardId: 0,
+      orderId: 0,
+      mode: '',
+      posterUrl: '',
+      posterLoading: false,
+      posterFail: false,
+      canvasW: 360,
+      canvasH: 640
+    }
+  },
+  computed: {
+    /** 非返余额时的页面标题 */
+    pageTitle() {
+      const t = Number(this.fundType)
+      if (t === 1) return '原路退回成功'
+      if (t === 3) return '冲销成功'
+      return '冲销成功'
+    },
+    /** 非返余额时的说明文案 */
+    simpleSuccessText() {
+      const t = Number(this.fundType)
+      const amt = this.formatMoney(this.amount)
+      if (t === 1) return `已原路退回 ¥${amt}`
+      if (t === 3) return `冲销 ¥${amt},仅记账(资金未变动)`
+      return `冲销成功,金额 ¥${amt}`
+    }
+  },
+  onLoad(option) {
+    this.initPayload(option || {})
+  },
+  methods: {
+    /**
+     * 合并 URL 参数与本地缓存,解析页面所需字段
+     * @param {Object} option 路由 query
+     */
+    initPayload(option) {
+      let cached = {}
+      try {
+        const raw = uni.getStorageSync(STORAGE_KEY)
+        if (raw) {
+          cached = typeof raw === 'string' ? JSON.parse(raw) : raw
+        }
+      } catch (e) {
+        cached = {}
+      }
+      const payload = Object.assign({}, cached, option || {})
+
+      this.fundType = Number(payload.fundType || 0)
+      this.amount = parseFloat(payload.amount || payload.actPrice || 0) || 0
+      this.customId = Number(payload.customId || 0)
+      this.customName = this.decodeName(payload.customName || '')
+      this.customBalance = parseFloat(payload.customBalance || 0) || 0
+      // forwardSn 优先取冲销单号,售后场景可传 refund orderSn
+      this.forwardSn = payload.forwardSn || payload.orderSn || ''
+      this.forwardId = Number(payload.forwardId || payload.refundId || 0)
+      this.orderId = Number(payload.orderId || 0)
+      this.mode = payload.mode || ''
+
+      // 返余额且有客户 id 时自动生成海报
+      if (Number(this.fundType) === 2 && this.customId > 0) {
+        this.generatePoster()
+      }
+    },
+
+    /** 安全解码客户名 */
+    decodeName(name) {
+      if (!name) return ''
+      try {
+        return decodeURIComponent(name)
+      } catch (e) {
+        return name
+      }
+    },
+
+    /** 金额保留两位小数展示 */
+    formatMoney(val) {
+      const n = parseFloat(val)
+      if (isNaN(n)) return '0.00'
+      return n.toFixed(2)
+    },
+
+    /**
+     * 拉取小程序码与客户最新余额,下载二维码后绘制海报
+     * 副作用:更新 posterUrl / posterLoading / posterFail / customBalance
+     */
+    generatePoster() {
+      const that = this
+      if (!that.customId) {
+        that.posterFail = true
+        return
+      }
+      that.posterLoading = true
+      that.posterFail = false
+      that.posterUrl = ''
+
+      Promise.all([
+        getGatheringMiniCode({ id: that.customId }),
+        getUserDet({ id: that.customId })
+      ])
+        .then(([codeRes, userRes]) => {
+          if (!codeRes || codeRes.code != 1) {
+            throw new Error((codeRes && codeRes.msg) || '获取小程序码失败')
+          }
+          if (!userRes || userRes.code != 1) {
+            throw new Error((userRes && userRes.msg) || '获取客户信息失败')
+          }
+          // 用接口最新余额覆盖路由传入值
+          if (userRes.data && userRes.data.balance !== undefined && userRes.data.balance !== null) {
+            that.customBalance = parseFloat(userRes.data.balance) || 0
+          }
+          const imgUrl = (codeRes.data && (codeRes.data.imgUrl || codeRes.data.url)) || ''
+          if (!imgUrl) {
+            throw new Error('未获取到小程序码链接')
+          }
+          return that.downloadImage(imgUrl)
+        })
+        .then((qrPath) => {
+          return that.drawPoster(qrPath)
+        })
+        .catch(() => {
+          that.posterLoading = false
+          that.posterFail = true
+        })
+    },
+
+    /**
+     * 下载远程图片到本地临时路径,供 canvas drawImage 使用
+     * @param {String} url 图片地址
+     * @returns {Promise<String>}
+     */
+    downloadImage(url) {
+      return new Promise((resolve, reject) => {
+        uni.downloadFile({
+          url,
+          success(res) {
+            if (res.statusCode === 200 && res.tempFilePath) {
+              resolve(res.tempFilePath)
+            } else {
+              reject(new Error('download fail'))
+            }
+          },
+          fail() {
+            reject(new Error('download fail'))
+          }
+        })
+      })
+    },
+
+    /**
+     * 在 canvas 上绘制退款凭证海报并导出临时图片
+     * @param {String} qrPath 小程序码本地路径
+     * @returns {Promise<void>}
+     */
+    drawPoster(qrPath) {
+      const that = this
+      const ctx = uni.createCanvasContext('forwardPosterCanvas', this)
+      const w = that.canvasW
+      const h = that.canvasH
+      const headerH = 130
+      const padX = 24
+      const contentW = w - padX * 2
+
+      // 整页白底 + 顶部蓝色标题区
+      ctx.setFillStyle('#ffffff')
+      ctx.fillRect(0, 0, w, h)
+      ctx.setFillStyle('#3385ff')
+      ctx.fillRect(0, 0, w, headerH)
+
+      ctx.setFillStyle('#ffffff')
+      ctx.setTextAlign('center')
+      ctx.setFontSize(26)
+      ctx.fillText('退款凭证', w / 2, 48)
+      ctx.setFontSize(22)
+      const nameText = that.customName || '客户'
+      ctx.fillText(nameText.length > 14 ? nameText.substr(0, 14) + '…' : nameText, w / 2, 88)
+
+      // 退款金额(红色大号)
+      ctx.setFillStyle('#e02020')
+      ctx.setFontSize(44)
+      ctx.fillText('¥' + that.formatMoney(that.amount), w / 2, 168)
+
+      ctx.setFillStyle('#333333')
+      ctx.setFontSize(22)
+      ctx.fillText('已返充到余额', w / 2, 208)
+
+      // 信息区起始 Y
+      let rowY = 252
+      const labelX = padX
+      const valueX = w - padX
+      const rowGap = 36
+
+      ctx.setTextAlign('left')
+      that.drawInfoRow(ctx, '最新余额', '¥' + that.formatMoney(that.customBalance), labelX, valueX, rowY, false)
+      rowY += rowGap
+
+      // 冲销单号:标签与单号同一行,单号字号自适应
+      ctx.setFillStyle('#666666')
+      ctx.setFontSize(20)
+      ctx.setTextAlign('left')
+      ctx.fillText('冲销单号', labelX, rowY)
+      const snText = that.forwardSn || '-'
+      const snStartX = labelX + 88
+      const snMaxW = valueX - snStartX
+      ctx.setFillStyle('#333333')
+      ctx.setTextAlign('left')
+      const snSize = that.fitSnFontSize(ctx, snText, snMaxW, 16)
+      ctx.setFontSize(snSize)
+      ctx.fillText(snText, snStartX, rowY)
+      rowY += rowGap
+
+      const dateStr = dayjs().format('YYYY-MM-DD HH:mm')
+      that.drawInfoRow(ctx, '日期', dateStr, labelX, valueX, rowY, true)
+      rowY += rowGap + 16
+
+      // 小程序码
+      const qrSize = 180
+      const qrX = (w - qrSize) / 2
+      const qrY = rowY
+      ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize)
+
+      // 底部提示
+      ctx.setFillStyle('#999999')
+      ctx.setFontSize(18)
+      ctx.setTextAlign('center')
+      ctx.fillText('长按识别查看余额明细', w / 2, qrY + qrSize + 36)
+
+      return new Promise((resolve, reject) => {
+        ctx.draw(false, () => {
+          setTimeout(() => {
+            uni.canvasToTempFilePath(
+              {
+                canvasId: 'forwardPosterCanvas',
+                fileType: 'jpg',
+                quality: 1,
+                success(res) {
+                  that.posterUrl = res.tempFilePath
+                  that.posterLoading = false
+                  that.posterFail = false
+                  resolve()
+                },
+                fail() {
+                  that.posterLoading = false
+                  that.posterFail = true
+                  reject(new Error('export fail'))
+                }
+              },
+              that
+            )
+          }, 300)
+        })
+      })
+    },
+
+    /**
+     * 绘制一行 label + value(value 右对齐)
+     * @param {Object} ctx canvas 上下文
+     * @param {String} label 左侧标签
+     * @param {String} value 右侧值
+     * @param {Number} labelX 标签 x
+     * @param {Number} valueX 值右边界 x
+     * @param {Number} y 基线 y
+     * @param {Boolean} valueSmall 值是否用小号字(16)
+     */
+    drawInfoRow(ctx, label, value, labelX, valueX, y, valueSmall) {
+      ctx.setFillStyle('#666666')
+      ctx.setFontSize(20)
+      ctx.setTextAlign('left')
+      ctx.fillText(label, labelX, y)
+      ctx.setFillStyle('#333333')
+      ctx.setFontSize(valueSmall ? 16 : 20)
+      ctx.setTextAlign('right')
+      ctx.fillText(value, valueX, y)
+    },
+
+    /**
+     * 冲销单号过长时缩小字号,保证同一行展示完整
+     * @param {Object} ctx canvas 上下文
+     * @param {String} text 单号文本
+     * @param {Number} maxWidth 可用宽度(px)
+     * @param {Number} startSize 起始字号
+     * @returns {Number} 最终使用的字号
+     */
+    fitSnFontSize(ctx, text, maxWidth, startSize) {
+      let size = startSize || 16
+      const minSize = 10
+      ctx.setFontSize(size)
+      while (size > minSize && ctx.measureText(text).width > maxWidth) {
+        size -= 1
+        ctx.setFontSize(size)
+      }
+      return size
+    },
+
+    /** 保存海报到系统相册 */
+    saveToAlbum() {
+      const that = this
+      if (!that.posterUrl) {
+        that.$msg && that.$msg('请等待凭证生成完成')
+        return
+      }
+      uni.showLoading({ title: '正在保存...', mask: true })
+      uni.saveImageToPhotosAlbum({
+        filePath: that.posterUrl,
+        success() {
+          uni.hideLoading()
+          uni.showToast({ title: '保存成功', icon: 'success' })
+        },
+        fail() {
+          uni.hideLoading()
+          uni.showToast({ title: '保存失败,请检查相册权限', icon: 'none' })
+        }
+      })
+    },
+
+    /** 返回上一页或工作台首页 */
+    goBack() {
+      if (this.mode === 'repost') {
+        uni.navigateBack({ delta: 1 })
+        return
+      }
+      this.$util.pageTo({
+        url: '/admin/home/workbench',
+        type: 4,
+        query: { tabIndex: 0 }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.forward-result-page {
+  min-height: 100vh;
+  background-color: #f5f5f5;
+}
+
+.status-text {
+  margin-bottom: 30upx;
+  font-size: 28upx;
+  color: #666666;
+  text-align: center;
+}
+
+.fail-box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+}
+
+.fail-text {
+  color: #e02020;
+}
+
+.poster-preview {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+  margin-bottom: 30upx;
+}
+
+.poster-img {
+  width: 640upx;
+  max-width: 100%;
+  border-radius: 12upx;
+  box-shadow: 0 8upx 24upx rgba(0, 0, 0, 0.12);
+}
+
+.poster-tip {
+  margin-top: 20upx;
+  font-size: 24upx;
+  color: #999999;
+  text-align: center;
+}
+
+.simple-success {
+  margin-bottom: 40upx;
+  font-size: 30upx;
+  color: #333333;
+  text-align: center;
+  line-height: 1.6;
+}
+
+.action-btn {
+  margin-bottom: 30upx;
+  width: 100%;
+}
+
+/* 离屏 canvas,不参与页面布局 */
+.poster-canvas {
+  position: fixed;
+  left: -9999px;
+  top: -9999px;
+  z-index: -1;
+}
+</style>

+ 13 - 0
ghsApp/src/admin/home/components/OrderItem.vue

@@ -7,6 +7,8 @@
         <image class="avatar" mode="aspectFit" :src="item.customAvatarUrl"></image>
         <view class="tit"> {{ item.customName || '' }}</view>
         <view class="other-refund" v-if="item.refund == 2">有售后</view>
+        <!-- 隔天累计退款:有冲销角标 -->
+        <view class="other-forward" v-if="Number(item.nextDayTkPrice) > 0">有冲销</view>
         <view class="other-status" v-if="item.debt == 1">挂账</view>
         <text class="other-book" v-if="item.book == 1">预订单</text>
         <text class="other-presell" v-if="item.presell == 1">预售单</text>
@@ -725,6 +727,17 @@ export default {
     margin-right: 20upx;
     width: auto;
   }
+  & > .other-forward {
+    text-align: center;
+    color: white;
+    background-color: #e67e22;
+    border: 2upx solid #e67e22;
+    border-radius: 30upx;
+    padding: 5upx 12upx;
+    font-size: 20upx;
+    margin-right: 20upx;
+    width: auto;
+  }
   & > .other-status {
     text-align: center;
     color: white;

+ 5 - 0
ghsApp/src/api/custom/index.js

@@ -12,6 +12,11 @@ export const getGatheringCode = data => {
 	return https.get('/custom/get-gathering-code', data)
 }
 
+/** 隔天冲销返余额海报用小程序码(含 balance) */
+export const getGatheringMiniCode = data => {
+	return https.get('/custom/get-gathering-mini-code', data)
+}
+
 export const getCustomBalanceChange = data => {
 	return https.get('/custom-balance-change/list', data)
 }

+ 5 - 0
ghsApp/src/api/refund/index.js

@@ -43,4 +43,9 @@ export const getRefundListByItem = data => {
 
 export const changeRefundOption = data => {
 	return https.post('/refund/change-refund-option', data)
+}
+
+/** 原单是否可走隔天冲销 */
+export const checkNextDayEligible = data => {
+	return https.get('/refund/check-next-day-eligible', data)
 }

+ 145 - 0
ghsApp/src/components/forward-options-popup.vue

@@ -0,0 +1,145 @@
+<!--
+  隔天冲销选项弹框
+  用途:售后页确认隔天冲销时选择资金处理方式(原路/返余额/仅记账)
+-->
+<template>
+	<view v-if="show" class="mask" @click="onCancel">
+		<view class="panel" @click.stop>
+			<view class="title">冲销确认</view>
+			<view class="tip">{{ tipText }}</view>
+			<view class="label">资金处理</view>
+			<view
+				v-for="(opt, idx) in fundOptions"
+				:key="idx"
+				class="opt"
+				:class="{ active: fundType === opt.value }"
+				@click="fundType = opt.value"
+			>
+				<text>{{ opt.label }}</text>
+			</view>
+			<view class="btns">
+				<button class="admin-button-com" @click="onCancel">取消</button>
+				<button class="admin-button-com blue" @click="onOk">确认冲销</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'forward-options-popup',
+	props: {
+		show: { type: Boolean, default: false },
+		// 是否在线支付原单(可原路退)
+		allowOriginal: { type: Boolean, default: false },
+		// 挂账/余额强制返余额时隐藏其它选项
+		forceBalance: { type: Boolean, default: false },
+		// 已结账单当天售后也走冲销
+		orderCleared: { type: Boolean, default: false }
+	},
+	data() {
+		return {
+			fundType: 2
+		}
+	},
+	computed: {
+		tipText() {
+			if (this.orderCleared) {
+				return '订单已结账,按冲销处理:不扣减订单实付,金额记入隔天累计退款。'
+			}
+			return '隔天冲销不扣减订单实付,金额记入隔天累计退款。'
+		},
+		fundOptions() {
+			if (this.forceBalance) {
+				return [{ value: 2, label: '返充到客户余额' }]
+			}
+			const list = [
+				{ value: 2, label: '返充到客户余额' },
+				{ value: 3, label: '仅记账(不动资金)' }
+			]
+			if (this.allowOriginal) {
+				list.unshift({ value: 1, label: '原路退回' })
+			}
+			return list
+		}
+	},
+	watch: {
+		show(v) {
+			if (v) {
+				this.fundType = this.forceBalance || !this.allowOriginal ? 2 : 1
+			}
+		}
+	},
+	methods: {
+		onCancel() {
+			this.$emit('cancel')
+		},
+		onOk() {
+			this.$emit('confirm', { fundType: this.fundType })
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.mask {
+	position: fixed;
+	left: 0;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.45);
+	z-index: 999;
+	display: flex;
+	align-items: flex-end;
+}
+.panel {
+	width: 100%;
+	background: #fff;
+	border-radius: 24upx 24upx 0 0;
+	padding: 30upx;
+	padding-bottom: calc(30upx + env(safe-area-inset-bottom));
+	box-sizing: border-box;
+}
+.title {
+	font-size: 32upx;
+	font-weight: bold;
+	color: #333;
+}
+.tip {
+	margin-top: 12upx;
+	font-size: 24upx;
+	color: #999;
+}
+.label {
+	margin-top: 28upx;
+	margin-bottom: 12upx;
+	font-size: 28upx;
+	color: #666;
+}
+.opt {
+	padding: 22upx 24upx;
+	margin-top: 12upx;
+	border: 1upx solid #e5e5e5;
+	border-radius: 12upx;
+	font-size: 28upx;
+	color: #333;
+}
+.opt.active {
+	border-color: #3385ff;
+	color: #3385ff;
+	background: #f0f7ff;
+}
+.btns {
+	display: flex;
+	flex-direction: row;
+	margin-top: 30upx;
+}
+.btns button {
+	flex: 1;
+	margin-left: 16upx;
+}
+.btns button:first-child {
+	margin-left: 0;
+}
+</style>

+ 2 - 1
ghsApp/src/pages.json

@@ -334,7 +334,8 @@
 				{ "path": "customerAffirm", "style": { "navigationBarTitleText": "确认下单" } },
 				{ "path": "confirm", "style": { "navigationBarTitleText": "待确认" } },
 				{"path": "toPay","style": {"navigationBarTitleText": "等待付款"}},
-				{"path": "affirmGhsBook","style": {"navigationBarTitleText": "等待付款"}}
+				{"path": "affirmGhsBook","style": {"navigationBarTitleText": "等待付款"}},
+				{ "path": "forwardResult", "style": { "navigationBarTitleText": "冲销凭证" } }
 			]
 		},
 		{

+ 104 - 17
ghsApp/src/pagesOrder/refund.vue

@@ -202,18 +202,30 @@
 			<button class="action-btn confirm-btn" @tap="confirmRefund">确定</button>
 		</view>
 
+		<!-- 隔天冲销资金选项 -->
+		<forward-options-popup
+			:show="showForwardPop"
+			:allow-original="allowOriginalFund"
+			:force-balance="forceBalanceFund"
+			:order-cleared="orderCleared"
+			@cancel="showForwardPop = false"
+			@confirm="onForwardConfirm"
+		/>
 	</view>
 </template>
 <script>
 import TuiListCell from "@/components/plugin/list-cell";
 import { getDetail,refund } from "@/api/order/index";
+import { checkNextDayEligible } from "@/api/refund";
 import { ORDER_STATUS } from "@/utils/declare";
 import { getUnClear } from "@/api/clear"
 import { changeHalf } from "@/api/item"
+import ForwardOptionsPopup from "@/components/forward-options-popup"
 export default {
 	name: "orderDetail",
 	components: {
-		TuiListCell
+		TuiListCell,
+		ForwardOptionsPopup
 	},
 	data() {
 		return {
@@ -225,7 +237,14 @@ export default {
 			remark:'',
 			orderInfo:{},
 			couldRefundPrice:0,
-			unClear:[]
+			unClear:[],
+			// 隔天冲销预检
+			nextDayEligible: false,
+			showForwardPop: false,
+			allowOriginalFund: false,
+			forceBalanceFund: false,
+			// 已结账(当天也走冲销)
+			orderCleared: false
 		};
 	},
 	onShow() {
@@ -319,7 +338,7 @@ export default {
 					return false
 				}
 			}
-			let refundParams = {
+			this._pendingRefundParams = {
 				id:this.option.id,
 				product:JSON.stringify(product),
 				price:this.refundMoney,
@@ -330,6 +349,11 @@ export default {
 				refundSendCost:this.orderInfo.refundSendCostAmount,
 				refundPackCost:this.orderInfo.refundPackCostAmount
 			};
+			// 可隔天冲销:弹资金选项;否则走当天售后
+			if (this.nextDayEligible) {
+				this.showForwardPop = true
+				return
+			}
 			let topTitle = '提示'
 			let title = '确认退款?'
 			if(this.orderInfo.payWay == 2){
@@ -339,17 +363,63 @@ export default {
 				topTitle = '特别注意!!!'
 			}
 			that.$util.confirmModal({title:topTitle,content:title},() => {
-				uni.showLoading({title: "提交中",mask:true})
-				refund(refundParams).then(res => {
-					if(res.code == 1){
-						uni.hideLoading()
-						if(res.data.error && res.data.error == 'hasUnClearOrder'){
-							that.needCancelRemind(res.data.clearId)
-						}else{
-							this.$util.pageTo({url: '/pagesOrder/refundSuccess?id='+res.data.id,type:2})
-						}
+				that.submitRefund(that._pendingRefundParams)
+			})
+		},
+		/**
+		 * 隔天冲销确认:写入 sameDay=0 + fundType 后提交
+		 */
+		onForwardConfirm(opt) {
+			this.showForwardPop = false
+			const params = Object.assign({}, this._pendingRefundParams || {}, {
+				sameDay: 0,
+				fundType: opt.fundType
+			})
+			this.submitRefund(params)
+		},
+		submitRefund(refundParams) {
+			const that = this
+			uni.showLoading({title: "提交中",mask:true})
+			refund(refundParams).then(res => {
+				uni.hideLoading()
+				if(res.code == 1){
+					if(res.data && res.data.error && res.data.error == 'hasUnClearOrder'){
+						that.needCancelRemind(res.data.clearId)
+						return
 					}
-				})
+					const data = res.data || {}
+					// 隔天返余额:进凭证海报页
+					if (Number(data.sameDay) === 0 && Number(data.fundType) === 2) {
+						uni.setStorageSync('forwardResultPayload', {
+							fundType: data.fundType,
+							amount: data.refundPrice || refundParams.price,
+							customId: data.customId || that.orderInfo.customId,
+							customBalance: data.customBalance != null ? data.customBalance : '',
+							customName: data.customName || that.orderInfo.customName || '',
+							forwardSn: data.orderSn || '',
+							forwardId: data.id || 0,
+							orderId: data.orderId || that.option.id
+						})
+						that.$util.pageTo({
+							url: '/admin/billing/forwardResult',
+							type: 2,
+							query: {
+								fundType: data.fundType,
+								amount: data.refundPrice || refundParams.price,
+								customId: data.customId || that.orderInfo.customId,
+								customBalance: data.customBalance != null ? data.customBalance : '',
+								customName: encodeURIComponent(data.customName || that.orderInfo.customName || ''),
+								forwardSn: data.orderSn || '',
+								forwardId: data.id || 0,
+								orderId: data.orderId || that.option.id
+							}
+						})
+						return
+					}
+					that.$util.pageTo({url: '/pagesOrder/refundSuccess?id='+(data.id||''),type:2})
+				}
+			}).catch(() => {
+				uni.hideLoading()
 			})
 		},
 		needCancelRemind(id){
@@ -364,10 +434,10 @@ export default {
 			this.orderInfo = {...res.data,refundPackCostAmount:'',refundSendCostAmount:''}
 			
 
-			this.couldRefundPrice = Number(res.data.orderPrice) - Number(res.data.tkPrice)
-
-			this.couldRefundPrice = this.couldRefundPrice.toFixed(2)
-			this.couldRefundPrice = parseFloat(this.couldRefundPrice)
+			// 可退:订单实付口径;隔天另有 nextDayTkPrice 占用
+			const nextDay = Number(res.data.nextDayTkPrice || 0)
+			this.couldRefundPrice = Number(res.data.actPrice || res.data.orderPrice) - nextDay
+			this.couldRefundPrice = parseFloat(this.couldRefundPrice.toFixed(2))
 
 			this.product = res.data.product.map(ele=>{
 				return {...ele,refundCount:null}
@@ -382,6 +452,23 @@ export default {
 				})
 			}
 
+			// 预检是否可走隔天冲销
+			checkNextDayEligible({ orderId: this.option.id }).then(r => {
+				if (r.code == 1 && r.data && r.data.ok) {
+					this.nextDayEligible = true
+					this.orderCleared = Number(r.data.orderCleared) === 1
+					const payWay = Number(r.data.payWay)
+					const online = Number(r.data.onlinePay)
+					this.forceBalanceFund = (payWay === 2 || payWay === 3)
+					this.allowOriginalFund = online === 1 && (payWay === 0 || payWay === 1) && !this.forceBalanceFund
+				} else {
+					this.nextDayEligible = false
+					this.orderCleared = false
+				}
+			}).catch(() => {
+				this.nextDayEligible = false
+				this.orderCleared = false
+			})
 		},
 		goClear(item){
 			this.$util.pageTo({url:'/admin/clear/customInfo?id='+item.id})

+ 1 - 0
hdApp/src/pagesPurchase/orderItem.vue

@@ -9,6 +9,7 @@
         </text>
       </view>
       <view v-if="info.refund == 2" class="status" style="color:red;border:1px solid red;border-radius:20upx;font-size:20upx;padding:5upx 12upx;margin-right:40upx;">有退款</view>
+      <view v-if="Number(info.nextDayTkPrice) > 0" class="status" style="color:#e67e22;border:1px solid #e67e22;border-radius:20upx;font-size:20upx;padding:5upx 12upx;margin-right:40upx;">有冲销</view>
       <view v-if="info.debt == 1" class="status" style="color:red;border:1px solid red;border-radius:20upx;font-size:20upx;padding:5upx 12upx;margin-right:40upx;">挂账</view>
       <text v-if="info.book == 1" class="status"  style="color:white;border:1px solid #657cac;border-radius:20upx;font-size:20upx;padding:5upx 12upx;margin-right:40upx;background:#657cac;">预订单</text>
       <view class="status" v-if="info.status == 3" style="color:green;font-weight:bold;">{{ getStatusName }} </view>

+ 6 - 1
hdApp/src/pagesPurchase/purDetails.vue

@@ -123,12 +123,17 @@
         </view>
 
         <view v-if="detailInfo.refundLog && detailInfo.refundLog == 1" class="order-info_box">
-          <view>累计退款:</view>
+          <view>当天退款:</view>
           <view style="color:red;font-weight:bold;font-size:30upx;">¥{{detailInfo.tkPrice?parseFloat(detailInfo.tkPrice):0}}</view>
             <view class="price" v-if="detailInfo.refundLog && detailInfo.refundLog == 1">
               <button @click="pageTo({url: '/pagesPurchase/refundList?id='+detailInfo.id})" class="admin-button-com">售后记录</button>
             </view>
         </view>
+        <!-- 隔天冲销累计(不影响实付) -->
+        <view v-if="Number(detailInfo.nextDayTkPrice) > 0" class="order-info_box">
+          <view>隔天冲销:</view>
+          <view style="color:#e67e22;font-weight:bold;font-size:30upx;">¥{{parseFloat(detailInfo.nextDayTkPrice)}}</view>
+        </view>
         <view v-if="detailInfo.refundLog && detailInfo.refundLog == 1" class="order-info_box">
           <view>退款方式:</view>
           <view style="color:red;font-weight:bold;font-size:30upx;">{{detailInfo.tkExplain||''}}</view>