Files
cashier_desktop/src/views/order/components/refundDrawer.vue
2025-12-04 16:14:59 +08:00

528 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-drawer v-model="isShow" direction="rtl" size="80%">
<template #header>
<h4>订单号{{ item.orderNo }}</h4>
</template>
<template #default>
<!-- <div class="order_info">
<div class="row"><span>订单类型</span>{{ filterLable('orderType', item.orderType) }}</div>
<div class="row"><span>平台类型</span>{{ filterLable('platformType', item.platformType) }}</div>
<div class="row"><span>用餐模式</span>{{ filterLable('dineMode', item.dineMode) }}</div>
<div class="row"><span>订单备注</span>{{ item.remark }}</div>
<div class="row"><span>支付类型</span>{{ filterLable('payType', item.payType) }}</div>
<div class="row"><span>支付单号</span>{{ item.payOrderNo }}</div>
<div class="row"><span>支付金额</span>{{ item.payAmount }}</div>
<div class="row"><span>支付时间</span>{{ item.paidTime }}</div>
<div class="row"><span>订单金额含折扣</span>{{ item.orderAmount }}</div>
<div class="row"><span>订单原金额不含折扣</span>{{ item.originAmount }}</div>
</div> -->
<div class="table">
<div class="tab_head">
<el-radio-group v-model="refundType" @change="refundTypeChange">
<el-radio-button label="整单退" :value="1" />
<el-radio-button label="部分退" :value="2" />
<el-radio-button label="自定义" :value="3" />
</el-radio-group>
<div class="amount">
<el-input ref="amountInputRef" v-model="refundAmount" style="width: 400px;height: 42px;"
:readonly="refundType != 3" placeholder="请输入退款金额" @input="inputChange">
<template #prepend></template>
<template #append>最多可退{{ formatDecimal(item.payAmount - item.refundAmount, 2) }}</template>
</el-input>
<!-- <template>
退款金额{{ formatDecimal(refundType == 1 ? item.originAmount - item.refundAmount : amount) }}
</template> -->
</div>
</div>
<el-table ref="tableRef" :data="item.onGoods" brder stripe @selection-change="tabSelectChange">
<el-table-column type="selection" width="35" :selectable="selectable"></el-table-column>
<el-table-column label="商品信息">
<template v-slot="scope">
<div class="goods_info">
<el-image :src="scope.row.productImg" style="width: 50px;height: 50px;"></el-image>
<div class="info">
<div class="name">
<span>{{ scope.row.productName }}</span>
</div>
<div class="sku" v-if="scope.row.skuName">{{ scope.row.skuName }}</div>
<div class="sku">{{ formatDecimal(+scope.row.unitPrice) }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="订单数量" prop="num" width="100"></el-table-column>
<el-table-column label="支付金额" width="100">
<template v-slot="scope">
{{ formatDecimal(+scope.row.payAmount) }}
</template>
</el-table-column>
<el-table-column label="退款数量" width="170">
<template v-slot="scope">
<el-input-number v-model="scope.row.refund_number" :disabled="refundType != 2" :min="0"
:max="formatDecimal(scope.row.num - scope.row.refundNum - scope.row.returnNum, 2, true)"
style="width: 130px;" @change="numberChange">
</el-input-number>
</template>
</el-table-column>
</el-table>
<template v-if="item.returnGoods.length">
<div class="tips" style="margin-top: 20px;padding-bottom: 10px;">以下为已退部分退单/退菜</div>
<el-table :data="item.returnGoods" brder stripe>
<el-table-column label="商品信息">
<template v-slot="scope">
<div class="goods_info">
<el-image :src="scope.row.productImg" style="width: 50px;height: 50px;"></el-image>
<div class="info">
<div class="name">
<span>{{ scope.row.productName }}</span>
</div>
<div class="sku" v-if="scope.row.skuName">{{ scope.row.skuName }}</div>
<div class="sku">{{ formatDecimal(+scope.row.unitPrice) }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="订单数量" prop="num" width="100"></el-table-column>
<el-table-column label="支付金额" width="100">
<template v-slot="scope">
{{ formatDecimal(+scope.row.payAmount) }}
</template>
</el-table-column>
<el-table-column label="已退款数量" width="170">
<template v-slot="scope">
<div class="column">
<div class="row">退单数量{{ scope.row.refundNum }}</div>
<div class="row">退菜数量{{ scope.row.returnNum }}</div>
</div>
</template>
</el-table-column>
</el-table>
</template>
<div class="ipt">
<el-input type="textarea" :rows="4" v-model="remark" placeholder="请输入退单原因" />
</div>
<div class="remark_tag">
<div class="item" v-for="(item, index) in remarkTagList" :key="index" @click="addRmarkHandle(item)">
{{ item }}
</div>
<div class="item" @click="remark = ''">
<el-icon>
<CircleClose />
</el-icon>
清空
</div>
</div>
</div>
</template>
<template #footer>
<div class="drawer_footer">
<div class="btn">
<el-button type="danger" style="width: 100%;" :loading="loading"
@click="handleRefund">手动退款</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="loading"
@click="refundHandle()">原路退回</el-button>
</div>
</div>
</template>
</el-drawer>
<takeFoodCode ref="takeFoodCodeRef" title="退款密码" :type="2" input-type="password" placeholder="请输入退款密码"
@success="passwordSuccess" />
</template>
<script setup>
import _ from 'lodash'
import { ref } from 'vue'
import { useGlobal } from '@/store/global.js'
import { formatDecimal, inputFilterFloat } from "@/utils/index.js";
import { refundOrder, orderPrint } from '@/api/order.js'
import { ElMessageBox, ElMessage } from 'element-plus'
import { usePrint } from "@/store/print.js";
import { useUser } from '@/store/user.js'
import dayjs from 'dayjs'
import takeFoodCode from "@/components/takeFoodCode.vue";
const emits = defineEmits(['success'])
const store = useUser()
const printStore = usePrint();
const globalStore = useGlobal()
const isShow = ref(false)
const item = ref({})
const tableRef = ref(null)
const modify = ref(false)
const refundAmount = ref('')
const refundType = ref(1)
const remark = ref('')
const remarkTagList = ref([
'顾客取消',
'等待时间长',
'支付错误',
'商品不满意',
'服务态度不满意',
'打包费'
])
const loading = ref(false)
const takeFoodCodeRef = ref(null)
const cash = ref(false)
const amountInputRef = ref(null)
// 退款密码
async function passwordSuccess(e = '') {
try {
loading.value = true
let rows = tableRef.value.getSelectionRows()
let refundDetails = []
if (refundType.value != 1) {
refundDetails = tableRef.value.getSelectionRows().map(val => {
return {
id: val.id,
returnAmount: val.payAmount,
num: val.refund_number
}
})
}
let data = {
orderId: item.value.id,
refundAmount: formatDecimal(+refundAmount.value),
modify: modify.value,
cash: cash.value,
refundReason: remark.value,
refundDetails: refundDetails,
pwd: e,
};
await refundOrder(data)
ElMessage.success('退款成功')
await printRefund(rows)
isShow.value = false
emits('success')
} catch (error) {
console.log(error);
}
loading.value = false
}
// 显示手动退款
function handleRefund() {
ElMessageBox.confirm('请线下手动转账给客户或现金,一旦操作完成无法修改订单状态,请慎重操作!', '注意', {
confirmButtonText: '已在线下完成退款'
}).then(() => {
cash.value = true
refundHandle()
}).catch(() => { })
}
// 开始退款
async function refundHandle(cash = false) {
try {
let rows = tableRef.value.getSelectionRows()
if (refundType.value == 2) {
if (!rows.length) {
ElMessage.error('请选择退款商品')
return
}
}
if (!refundAmount.value) {
ElMessage.error('请请输入退款金额')
return
}
if (!remark.value) {
ElMessage.error('请请输入退款原因')
return
}
if (refundAmount.value <= 0) {
ElMessage.error('无可退金额')
return
}
loading.value = true
await store.getShopInfo()
if (store.shopInfo.isReturnPwd == 1) {
takeFoodCodeRef.value.show();
} else {
passwordSuccess()
}
} catch (error) {
console.log(error);
}
loading.value = false
}
// 打印退款小票
async function printRefund(rows) {
try {
if (printStore.deviceNoteList.length) {
// 本地打印
const data = {
shop_name: store.shopInfo.shopName,
loginAccount: store.userInfo.name,
carts: [],
amount: formatDecimal(+refundAmount.value),
remark: remark.value,
orderInfo: item.value,
outNumber: item.value.id,
createdAt: item.value.createTime,
printTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
}
rows.map(item => {
data.carts.push(
{
name: item.productName,
number: item.num,
skuName: item.skuName,
salePrice: formatDecimal(+item.unitPrice),
totalAmount: formatDecimal(+item.payAmount)
}
)
})
printStore.printRefund(data);
} else {
// 云打印
await orderPrint({ id: item.value.id, type: 2 })
ElMessage.success('云打印退款单成功')
}
} catch (error) {
console.log(error);
}
}
// 数量变化
function numberChange() {
let rows = tableRef.value.getSelectionRows()
tabSelectChange(rows)
}
// 选择退款项
const tabSelectChange = _.debounce(function (val) {
refundAmount.value = ''
let num = 0
val.map(item => {
if ((+item.refundNum + +item.returnNum) < item.num) {
num += item.refund_number * item.unitPrice
}
})
if (num) {
if (num >= item.value.payAmount) {
refundAmount.value = formatDecimal(item.value.payAmount - item.value.refundAmount)
} else {
refundAmount.value = formatDecimal(num)
}
}
}, 100)
// 禁用所有行的选择
const selectable = (row, index) => {
return refundType.value == 2;
};
// 初始化抽屉
function resetDrawer() {
modify.value = false
refundAmount.value = ''
refundType.value = 1
remark.value = ''
cash.value = false
}
function show(row) {
resetDrawer()
isShow.value = true
let newRow = { ...row }
remark.value = ''
let onGoods = []
let returnGoods = []
newRow.goods.map(item => {
// 可退的最大数量,下单数量 - 已退数量 - 退菜数量
let refundMaxNum = item.num - item.refundNum - item.returnNum
if (refundMaxNum <= 0) {
item.refund_number = item.num
// 已经退过,不在允许操作
returnGoods.push(item)
} else {
// 可以操作的退款数量
item.refund_number = item.num
onGoods.push(item)
}
})
newRow.onGoods = onGoods
newRow.returnGoods = returnGoods
item.value = newRow
setTimeout(() => {
tableRef.value.clearSelection()
refundTypeChange(1)
}, 100);
}
// 筛选类型
function filterLable(key, type) {
let item = globalStore[key].find(item => item.type == type)
if (item && item.type) {
return item.label
} else {
return type
}
}
// 切换退单类型
function refundTypeChange(val) {
switch (val) {
case 1:
modify.value = false
// tableRef.value.toggleAllSelection()
// 遍历数据,将每一行设置为选中状态
item.value.onGoods.forEach((row) => {
tableRef.value.toggleRowSelection(row, true);
});
break;
case 2:
modify.value = false
if (item.value.onGoods.length == 1) {
item.value.onGoods.forEach((row) => {
tableRef.value.toggleRowSelection(row, true);
});
} else {
tableRef.value.clearSelection()
}
break;
case 3:
tableRef.value.clearSelection()
refundAmount.value = ''
modify.value = true
amountInputRef.value.focus()
break;
default:
break;
}
}
// 自定义退款金额输入
const inputChange = _.debounce(function (n) {
if (n > item.value.payAmount - item.value.refundAmount) {
refundAmount.value = formatDecimal(item.value.payAmount - item.value.refundAmount, 2)
} else {
refundAmount.value = inputFilterFloat(n)
}
}, 300)
// 添加备注
function addRmarkHandle(item) {
if (remark.value.length) {
remark.value += `${item}`
} else {
remark.value = item
}
}
defineExpose({
show
})
// onMounted(() => {
// setTimeout(() => {
// refundTypeChange(1)
// }, 100)
// })
</script>
<style scoped lang="scss">
.drawer_footer {
display: flex;
gap: var(--el-font-size-base);
.btn {
flex: 1;
}
}
.order_info {
.row {
margin-bottom: 4px;
span {
color: #555;
}
}
}
.table {
padding: var(--el-font-size-base) 0;
.ipt {
padding: var(--el-font-size-base) 0;
}
.tab_head {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: var(--el-font-size-base);
.amount {
color: var(--el-color-danger);
font-weight: bold;
}
}
.remark_tag {
display: flex;
flex-wrap: wrap;
gap: 10px;
.item {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 6px 12px;
border: 1px solid #ddd;
background-color: #fff;
border-radius: 4px;
color: #555;
&:active {
background-color: #ddd;
}
}
}
}
.goods_info {
display: flex;
align-items: center;
.info {
flex: 1;
display: flex;
flex-direction: column;
padding-left: 6px;
.name {
display: flex;
justify-content: space-between;
.n {
color: #999;
}
}
.sku {
color: #999;
font-size: 14px;
}
}
}
</style>