|
|
@@ -1,13 +1,33 @@
|
|
|
<!--
|
|
|
- 隔天冲销结果页 / 退款凭证海报
|
|
|
- 用途:供货商端隔天冲销(fundType=2 返充余额)成功后,用 canvas 绘制退款凭证并展示、保存相册;
|
|
|
- 支持 mode=repost 从详情页重新生成凭证。
|
|
|
- 谁用:admin/billing 冲销流程、售后转冲销、凭证详情重发入口。
|
|
|
+ 退款结果页 / 资金落地 / 返充凭证海报
|
|
|
+ 用途:
|
|
|
+ 1) 无原单退款成功后:选手选「我已线下转账」或「返充到客户余额」
|
|
|
+ 2) 已返充余额:绘制退款凭证海报
|
|
|
+ 3) 已原路退回:简单成功提示
|
|
|
+ 谁用:freeRefundConfirm、售后详情/列表补发、隔天售后待落地资金。
|
|
|
-->
|
|
|
<template>
|
|
|
<view class="forward-result-page">
|
|
|
- <!-- fundType=2:展示凭证海报 -->
|
|
|
- <appResult v-if="Number(fundType) === 2" title="退款成功">
|
|
|
+ <!-- 待资金落地:原路确认 or 返充余额 -->
|
|
|
+ <appResult v-if="showFundActions" title="退款成功">
|
|
|
+ <text class="simple-success">已退款 ¥{{ formatMoney(amount) }},请选择资金退还方式</text>
|
|
|
+ <text class="fund-tip">原单:{{ payWayLabel }}</text>
|
|
|
+ <button
|
|
|
+ class="admin-button-com big blue action-btn"
|
|
|
+ :disabled="fundActing"
|
|
|
+ @click="onMarkHasReturn"
|
|
|
+ >已线下转账</button>
|
|
|
+ <button
|
|
|
+ v-if="Number(couldReturn) === 1"
|
|
|
+ class="admin-button-com big action-btn"
|
|
|
+ :disabled="fundActing"
|
|
|
+ @click="onReturnBalance"
|
|
|
+ >不想转账,返充到客户余额</button>
|
|
|
+ <button class="admin-button-com big action-btn" @click="goBack">返回首页</button>
|
|
|
+ </appResult>
|
|
|
+
|
|
|
+ <!-- 已返充余额:凭证海报 -->
|
|
|
+ <appResult v-else-if="showPoster" title="退款成功">
|
|
|
<view v-if="posterLoading" class="status-text">凭证生成中...</view>
|
|
|
<view v-else-if="posterFail" class="fail-box">
|
|
|
<text class="status-text fail-text">凭证生成失败,请重试</text>
|
|
|
@@ -37,7 +57,7 @@
|
|
|
</button>
|
|
|
</appResult>
|
|
|
|
|
|
- <!-- 非返余额:简单成功提示 -->
|
|
|
+ <!-- 已原路 / 其它终态 -->
|
|
|
<appResult v-else :title="pageTitle">
|
|
|
<text class="simple-success">{{ simpleSuccessText }}</text>
|
|
|
<button class="admin-button-com big action-btn" @click="goBack">
|
|
|
@@ -59,6 +79,7 @@ import dayjs from 'dayjs'
|
|
|
import appResult from '@/components/app-result'
|
|
|
import { getGatheringMiniCode } from '@/api/custom'
|
|
|
import { getUserDet } from '@/api/member'
|
|
|
+import { markHasReturn, returnBalance } from '@/api/refund'
|
|
|
|
|
|
/** 本地缓存键:跳转本页前写入,onLoad 与 URL 参数合并 */
|
|
|
const STORAGE_KEY = 'forwardResultPayload'
|
|
|
@@ -70,7 +91,6 @@ export default {
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
- fundType: 0,
|
|
|
amount: 0,
|
|
|
customId: 0,
|
|
|
customName: '',
|
|
|
@@ -79,6 +99,12 @@ export default {
|
|
|
forwardId: 0,
|
|
|
orderId: 0,
|
|
|
mode: '',
|
|
|
+ payWay: -1,
|
|
|
+ hasReturn: 0,
|
|
|
+ couldReturn: 1,
|
|
|
+ returnBalance: 0,
|
|
|
+ needFundAction: 0,
|
|
|
+ fundActing: false,
|
|
|
posterUrl: '',
|
|
|
posterLoading: false,
|
|
|
posterFail: false,
|
|
|
@@ -87,27 +113,37 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- /** 非返余额时的页面标题 */
|
|
|
+ /** 待选手选资金落地 */
|
|
|
+ showFundActions() {
|
|
|
+ return Number(this.hasReturn) === 0
|
|
|
+ && Number(this.returnBalance) === 0
|
|
|
+ && (Number(this.needFundAction) === 1 || Number(this.orderId) === 0)
|
|
|
+ },
|
|
|
+ /** 已返充:展示海报 */
|
|
|
+ showPoster() {
|
|
|
+ return Number(this.returnBalance) === 1
|
|
|
+ },
|
|
|
pageTitle() {
|
|
|
- const t = Number(this.fundType)
|
|
|
- // 无原单退款(orderId=0)用「退款」文案
|
|
|
- const free = Number(this.orderId) === 0
|
|
|
- if (t === 1) return '原路退回成功'
|
|
|
- if (t === 3) return free ? '退款成功' : '退款成功'
|
|
|
- return free ? '退款成功' : '退款成功'
|
|
|
+ if (Number(this.hasReturn) === 1) return '原路退回成功'
|
|
|
+ return '退款成功'
|
|
|
},
|
|
|
- /** 非返余额时的说明文案 */
|
|
|
simpleSuccessText() {
|
|
|
- const t = Number(this.fundType)
|
|
|
const amt = this.formatMoney(this.amount)
|
|
|
- const free = Number(this.orderId) === 0
|
|
|
- if (t === 1) return `已原路退回 ¥${amt}`
|
|
|
- if (t === 3) {
|
|
|
- return free
|
|
|
- ? `已退款 ¥${amt},请线下转账`
|
|
|
- : `已退款 ¥${amt},请线下转账`
|
|
|
+ if (Number(this.hasReturn) === 1) {
|
|
|
+ return `已原路退回 ¥${amt}`
|
|
|
+ }
|
|
|
+ return `退款成功,金额 ¥${amt}`
|
|
|
+ },
|
|
|
+ payWayLabel() {
|
|
|
+ const map = {
|
|
|
+ 0: '线下微信',
|
|
|
+ 1: '线下支付宝',
|
|
|
+ 2: '余额',
|
|
|
+ 3: '挂账',
|
|
|
+ 4: '现金',
|
|
|
+ 5: '银行卡'
|
|
|
}
|
|
|
- return free ? `退款成功,金额 ¥${amt}` : `退款成功,金额 ¥${amt}`
|
|
|
+ return map[Number(this.payWay)] || '线下转账'
|
|
|
}
|
|
|
},
|
|
|
onLoad(option) {
|
|
|
@@ -130,23 +166,51 @@ export default {
|
|
|
}
|
|
|
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 || ''
|
|
|
+ this.payWay = Number(payload.payWay != null ? payload.payWay : -1)
|
|
|
+ this.hasReturn = Number(payload.hasReturn || 0)
|
|
|
+ this.couldReturn = payload.couldReturn != null ? Number(payload.couldReturn) : 1
|
|
|
+ this.returnBalance = Number(payload.returnBalance || 0)
|
|
|
+ // 兼容旧缓存:fundType=2 视为已返充
|
|
|
+ if (!this.returnBalance && Number(payload.fundType) === 2) {
|
|
|
+ this.returnBalance = 1
|
|
|
+ }
|
|
|
+ this.needFundAction = Number(payload.needFundAction || 0)
|
|
|
+ if (!this.needFundAction && Number(this.hasReturn) === 0 && Number(this.returnBalance) === 0 && Number(this.orderId) === 0) {
|
|
|
+ this.needFundAction = 1
|
|
|
+ }
|
|
|
|
|
|
- // 返余额且有客户 id 时自动生成海报
|
|
|
- if (Number(this.fundType) === 2 && this.customId > 0) {
|
|
|
+ if (this.showPoster && this.customId > 0) {
|
|
|
this.generatePoster()
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ /** 同步缓存,避免返回再进丢状态 */
|
|
|
+ persistPayload() {
|
|
|
+ uni.setStorageSync(STORAGE_KEY, {
|
|
|
+ amount: this.amount,
|
|
|
+ customId: this.customId,
|
|
|
+ customBalance: this.customBalance,
|
|
|
+ customName: this.customName,
|
|
|
+ forwardSn: this.forwardSn,
|
|
|
+ forwardId: this.forwardId,
|
|
|
+ orderId: this.orderId,
|
|
|
+ mode: this.mode,
|
|
|
+ payWay: this.payWay,
|
|
|
+ hasReturn: this.hasReturn,
|
|
|
+ couldReturn: this.couldReturn,
|
|
|
+ returnBalance: this.returnBalance,
|
|
|
+ needFundAction: this.needFundAction
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
/** 安全解码客户名 */
|
|
|
decodeName(name) {
|
|
|
if (!name) return ''
|
|
|
@@ -158,9 +222,7 @@ export default {
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
- * 金额展示:最多两位小数,去掉末尾多余的 0(如 10.00→10、10.50→10.5)
|
|
|
- * @param {Number|String} val 金额
|
|
|
- * @returns {String}
|
|
|
+ * 金额展示:最多两位小数,去掉末尾多余的 0
|
|
|
*/
|
|
|
formatMoney(val) {
|
|
|
const n = parseFloat(val)
|
|
|
@@ -168,9 +230,73 @@ export default {
|
|
|
return String(parseFloat(n.toFixed(2)))
|
|
|
},
|
|
|
|
|
|
+ /** 我已线下转账 → hasReturn=1 */
|
|
|
+ onMarkHasReturn() {
|
|
|
+ const that = this
|
|
|
+ if (!that.forwardId) {
|
|
|
+ that.$msg && that.$msg('缺少退款单')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ that.$util.confirmModal({
|
|
|
+ content: '确认已线下转账给客户?',
|
|
|
+ okText: '已转账',
|
|
|
+ cancelText: '取消'
|
|
|
+ }, () => {
|
|
|
+ that.fundActing = true
|
|
|
+ uni.showLoading({ title: '提交中', mask: true })
|
|
|
+ markHasReturn({ id: that.forwardId }).then((res) => {
|
|
|
+ uni.hideLoading()
|
|
|
+ that.fundActing = false
|
|
|
+ if (res.code != 1) return
|
|
|
+ const data = res.data || {}
|
|
|
+ that.hasReturn = Number(data.hasReturn != null ? data.hasReturn : 1)
|
|
|
+ that.returnBalance = Number(data.returnBalance || 0)
|
|
|
+ that.needFundAction = 0
|
|
|
+ that.persistPayload()
|
|
|
+ that.$msg && that.$msg('已标记原路退回')
|
|
|
+ }).catch(() => {
|
|
|
+ uni.hideLoading()
|
|
|
+ that.fundActing = false
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ /** 不想转账,返充到客户余额 → returnBalance=1 + 海报 */
|
|
|
+ onReturnBalance() {
|
|
|
+ const that = this
|
|
|
+ if (!that.forwardId) {
|
|
|
+ that.$msg && that.$msg('缺少退款单')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ that.$util.confirmModal({
|
|
|
+ content: '确认不线下转账,将退款返充到客户余额?',
|
|
|
+ okText: '确认返充',
|
|
|
+ cancelText: '取消'
|
|
|
+ }, () => {
|
|
|
+ that.fundActing = true
|
|
|
+ uni.showLoading({ title: '返充中', mask: true })
|
|
|
+ returnBalance({ id: that.forwardId }).then((res) => {
|
|
|
+ uni.hideLoading()
|
|
|
+ that.fundActing = false
|
|
|
+ if (res.code != 1) return
|
|
|
+ const data = res.data || {}
|
|
|
+ that.returnBalance = Number(data.returnBalance != null ? data.returnBalance : 1)
|
|
|
+ that.hasReturn = Number(data.hasReturn || 0)
|
|
|
+ that.needFundAction = 0
|
|
|
+ if (data.customBalance != null) {
|
|
|
+ that.customBalance = parseFloat(data.customBalance) || 0
|
|
|
+ }
|
|
|
+ that.persistPayload()
|
|
|
+ that.generatePoster()
|
|
|
+ }).catch(() => {
|
|
|
+ uni.hideLoading()
|
|
|
+ that.fundActing = false
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
/**
|
|
|
* 拉取小程序码与客户最新余额,下载二维码后绘制海报
|
|
|
- * 副作用:更新 posterUrl / posterLoading / posterFail / customBalance
|
|
|
*/
|
|
|
generatePoster() {
|
|
|
const that = this
|
|
|
@@ -193,7 +319,6 @@ export default {
|
|
|
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
|
|
|
}
|
|
|
@@ -212,11 +337,6 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
|
|
|
- /**
|
|
|
- * 下载远程图片到本地临时路径,供 canvas drawImage 使用
|
|
|
- * @param {String} url 图片地址
|
|
|
- * @returns {Promise<String>}
|
|
|
- */
|
|
|
downloadImage(url) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
uni.downloadFile({
|
|
|
@@ -235,22 +355,14 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
|
|
|
- /**
|
|
|
- * 在 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
|
|
|
- // 顶部蓝区上下各收 4upx(画布宽 360 ≈ 750 设计宽一半,4upx≈2px)
|
|
|
const headerH = 126
|
|
|
const padX = 24
|
|
|
- const contentW = w - padX * 2
|
|
|
|
|
|
- // 整页白底 + 顶部蓝色标题区
|
|
|
ctx.setFillStyle('#ffffff')
|
|
|
ctx.fillRect(0, 0, w, h)
|
|
|
ctx.setFillStyle('#3385ff')
|
|
|
@@ -264,34 +376,28 @@ export default {
|
|
|
const nameText = that.customName || '客户'
|
|
|
ctx.fillText(nameText.length > 14 ? nameText.substr(0, 14) + '…' : nameText, w / 2, 86)
|
|
|
|
|
|
- // 退款金额(红色大号)
|
|
|
ctx.setFillStyle('#e02020')
|
|
|
ctx.setFontSize(44)
|
|
|
- // 金额距顶再下移 4upx(≈2px),与蓝区收紧后的间距对齐
|
|
|
ctx.fillText('¥' + that.formatMoney(that.amount), w / 2, 180)
|
|
|
|
|
|
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, '#3385ff')
|
|
|
rowY += rowGap
|
|
|
|
|
|
- // 冲销单号:标签左、单号右对齐;单号过长时缩小字号保证同一行完整展示
|
|
|
ctx.setFillStyle('#666666')
|
|
|
ctx.setFontSize(20)
|
|
|
ctx.setTextAlign('left')
|
|
|
ctx.fillText('退款单号', labelX, rowY)
|
|
|
const snText = that.forwardSn || '-'
|
|
|
- // 标签约占 88px,右侧留给单号;右对齐贴 valueX,与余额/日期一致
|
|
|
const snMaxW = valueX - (labelX + 88)
|
|
|
ctx.setFillStyle('#333333')
|
|
|
const snSize = that.fitSnFontSize(ctx, snText, snMaxW, 16)
|
|
|
@@ -304,13 +410,11 @@ export default {
|
|
|
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')
|
|
|
@@ -343,17 +447,6 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
|
|
|
- /**
|
|
|
- * 绘制一行 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)
|
|
|
- * @param {String} [valueColor='#333333'] 右侧数值颜色(如余额高亮蓝)
|
|
|
- */
|
|
|
drawInfoRow(ctx, label, value, labelX, valueX, y, valueSmall, valueColor) {
|
|
|
ctx.setFillStyle('#666666')
|
|
|
ctx.setFontSize(20)
|
|
|
@@ -365,14 +458,6 @@ export default {
|
|
|
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
|
|
|
@@ -384,7 +469,6 @@ export default {
|
|
|
return size
|
|
|
},
|
|
|
|
|
|
- /** 保存海报到系统相册 */
|
|
|
saveToAlbum() {
|
|
|
const that = this
|
|
|
if (!that.posterUrl) {
|
|
|
@@ -405,7 +489,6 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
|
|
|
- /** 返回上一页或工作台首页 */
|
|
|
goBack() {
|
|
|
if (this.mode === 'repost') {
|
|
|
uni.navigateBack({ delta: 1 })
|
|
|
@@ -468,19 +551,25 @@ export default {
|
|
|
}
|
|
|
|
|
|
.simple-success {
|
|
|
- margin-bottom: 40upx;
|
|
|
+ margin-bottom: 24upx;
|
|
|
font-size: 30upx;
|
|
|
color: #333333;
|
|
|
text-align: center;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
|
|
|
+.fund-tip {
|
|
|
+ margin-bottom: 40upx;
|
|
|
+ font-size: 34upx;
|
|
|
+ color: #505050;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
.action-btn {
|
|
|
margin-bottom: 30upx;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
-/* 离屏 canvas,不参与页面布局 */
|
|
|
.poster-canvas {
|
|
|
position: fixed;
|
|
|
left: -9999px;
|