Files
cashier_desktop/src/components/payCard/payCard.vue
2026-01-23 14:14:33 +08:00

1461 lines
53 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>
<div class="card">
<div class="header">
<div class="left">
<div class="t1">
<span class="title">应收:</span>
<span class="num">{{ formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount || 0) }}</span>
<!-- <div class="clear" v-if="money != props.amount" @click="emit('reset')">
<span style="margin-left: 10px;">清除优惠</span>
<el-icon style="margin-left: 6px">
<CircleClose />
</el-icon>
</div> -->
</div>
<div class="t2">
<span>商品原价:{{ goodsStore.cartInfo.costSummary.goodsRealAmount || 0 }}</span>
<span>餐位费:{{ formatDecimal(goodsStore.cartInfo.costSummary.seatFee || 0)
}}</span>
<span>打包费:{{ formatDecimal(goodsStore.cartInfo.costSummary.packFee || 0)
}}</span>
<span>优惠:{{ formatDecimal(goodsStore.cartInfo.costSummary.totalDiscountAmount || 0) }}</span>
<!-- <span v-if="goodsStore.cartInfo.costSummary.goodsDiscountAmount">
<span>折扣:{{ goodsStore.cartInfo.costSummary.goodsDiscountAmount }}</span>
</span> -->
</div>
</div>
</div>
<div class="number_wrap">
<div class="menus">
<div class="item" :class="{ active: payActive == index, disabled: item.disabled }"
v-for="(item, index) in payList" :key="item.id" @click="payTypeChange(index, item)">
<div class="icon">
<el-image :src="item.icon" class="img"></el-image>
</div>
<span class="title">{{ item.payName }}</span>
</div>
<!-- <div class="item" :class="{ active: payActive == 'buyer' }"
@click="payTypeChange('buyer', { payType: 'buyer' })">
<div class="icon">
<div class="img"
style="display: flex;align-items: center;justify-content: center;background-color: var(--el-color-danger);color: #fff;font-size: 24px;border-radius: 11px;">
</div>
</div>
<span class="title">挂账</span>
</div> -->
</div>
<div class="input_wrap">
<div class="input" style="flex: 1">付款{{ formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount
|| 0) }}</div>
<el-button type="primary" style="width: 120px;border-radius: 6px; height: 60px;"
@click="showCouponHandle">添加优惠</el-button>
</div>
<div class="blance">
<!-- 可用余额0.00 -->
</div>
<div class="keybord_wrap">
<div class="left">
<div class="item" v-for="item in 9" :key="item" @click="amountInput(`${item}`)">
{{ item }}
</div>
<div class="item" @click="amountInput('.')">.</div>
<div class="item" @click="amountInput('0')">0</div>
<div class="item" @click="delHandle">
<el-icon>
<Back />
</el-icon>
</div>
</div>
<div class="pay_btn" v-loading="payLoading" @click="confirmOrder">
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<scanModal ref="scanModalRef" :amount="props.amount" :money="money" :orderId="props.orderId"
:selecttype="props.selecttype" :payType="payType" :payData="payData" @success="scanCodeSuccess"
@orderExpired="emit('orderExpired')" />
<!-- 选择挂账人员 -->
<el-dialog title="挂账" top="1vh" v-model="showBuyer" width="90%" @closed="resetBuyerTable">
<el-form inline>
<el-form-item>
<el-input placeholder="请输入挂账人或手机号搜索" v-model="buyerTable.keywords" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getBuyerList">搜索</el-button>
<el-button @click="resetBuyerTable">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="buyerTable.list" height="440px" border stripe v-loading="buyerTable.loading">
<el-table-column prop="debtor" label="挂账人" width="100px" />
<el-table-column prop="mobile" label="手机" width="150px" />
<el-table-column prop="position" label="职位" width="120px" />
<el-table-column prop="repaymentMethod" label="还款方式" width="160px">
<template v-slot="scope">
<template v-if="scope.row.repaymentMethod == 'total'">按总金额还款</template>
<template v-if="scope.row.repaymentMethod == 'order'">按订单还款</template>
</template>
</el-table-column>
<el-table-column prop="creditAmount" label="挂账额度" width="160px">
<template v-slot="scope">
{{ formatDecimal(scope.row.creditAmount) }}
</template>
</el-table-column>
<el-table-column prop="remainingAmount" label="剩余挂账额度" width="160px">
<template v-slot="scope">
{{ formatDecimal(scope.row.remainingAmount) }}
</template>
</el-table-column>
<el-table-column prop="accumulateAmount" label="累计挂账金额" width="160px">
<template v-slot="scope">
{{ formatDecimal(scope.row.accumulateAmount) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120px" fixed="right">
<template v-slot="scope">
<el-button type="primary" @click="payCreditPayHandle(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination layout="prev, pager, next, total" background style="margin-top: 20px"
:total="Number(buyerTable.total)" v-model:current-page="buyerTable.page" @current-change="getBuyerList" />
</el-dialog>
<el-dialog v-model="showCoupon"
:title="`添加优惠(商品原价:¥${formatDecimal(goodsStore.cartInfo.costSummary.goodsRealAmount || 0)}`" top="5vh"
width="80%" @open="couponDialogOpen">
<div class="coupom_dialog">
<el-form ref="couponFormRef" :model="couponForm" label-width="100" label-position="left">
<el-form-item label="选择用户">
<div class="flex">
<div class="select_wrap">
<el-select placeholder="请选择用户" readonly v-model="couponFormUser.userId"
@click="SelectVipUserRef.show()" style="width: 100%;">
<el-option :label="item.nickName" :value="item.id" v-for="item in couponFormUserList"
:key="item.id"></el-option>
</el-select>
</div>
<el-button type="danger" @click="clearCouponUser">清除</el-button>
</div>
</el-form-item>
<el-form-item label="整单折扣">
<el-input v-model="couponForm.discountRatio" placeholder="请输入折扣" style="width: 180px;"
@input="discountInput">
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="优惠券">
<div style="width: 100%;">
<el-button type="primary"
v-if="(goodsStore.cartInfo.costSummary
.fullReduction !== undefined && goodsStore.cartInfo.costSummary.fullReduction.actualAmount > 0)"
disabled>参与满减活动不可用优惠券</el-button>
<el-button type="primary"
:disabled="(!couponFormUser.id && (!couponResList1.length && !couponResList2.length))"
@click="showCounponModalHandle" v-else>选择优惠券</el-button>
<div style="padding-top: 20px;">
<el-table :data="couponResList1" border stripe>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="抵扣" prop="discountAmount"></el-table-column>
<el-table-column label="限制" prop="fullAmount">
<template v-slot="scope">
满{{ scope.row.fullAmount }}减{{ scope.row.discountAmount }}
</template>
</el-table-column>
<el-table-column label="描述" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.useRestrictions"></div>
</template>
</el-table-column>
<el-table-column label="操作" width="110" align="center">
<template v-slot="scope">
<el-button type="danger" @click="delCoupon(scope.$index, 1)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- <div>
<div class="title">商品券</div>
<el-table :data="couponResList2" border stripe>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="商品信息">
<template v-slot="scope">
{{ scope.row.productName }}
</template>
</el-table-column>
<el-table-column label="抵扣" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.discount"></div>
</template>
</el-table-column>
<el-table-column label="描述" prop="useRestrictions">
<template v-slot="scope">
<div v-html="scope.row.useRestrictions"></div>
</template>
</el-table-column>
<el-table-column label="操作" width="110" align="center">
<template v-slot="scope">
<el-button type="danger" @click="delCoupon(scope.$index, 2)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div> -->
</div>
</el-form-item>
<el-form-item label="积分抵扣">
<div class="flex">
<el-input v-model="couponForm.pointsNum"
:disabled="!couponFormUser.id || !pointOptions.usable || goodsStore.cartInfo.costSummary.finalPayAmount < pointOptions.minPaymentAmount"
:placeholder="pointOptions.usable ? '请输入需要抵扣的积分' : pointOptions.unusableReason"
v-loading="pointOptions.loading" @input="pointInput">
<template #prepend>现有积分:{{ couponFormUser.accountPoints || 0 }}</template>
<template #append>可抵扣金额:¥{{ pointOptions.amount || 0 }}</template>
</el-input>
<el-button type="danger" @click="clearPoint">清除</el-button>
</div>
<div class="point_tips err" v-if="couponFormUser.id && !pointOptions.usable">
注意:{{ pointOptions.unusableReason || (`订单金额不足¥${formatDecimal(+pointOptions.minPaymentAmount)},无法使用积分抵扣`) }}
</div>
<div class="point_tips" v-if="couponFormUser.id && pointOptions.usable">
说明:订单已满足使用门槛,当前积分可抵扣最多¥{{ pointOptions.potentialAmount }}(当前积分 {{ couponFormUser.accountPoints || 0 }}
</div>
</el-form-item>
</el-form>
</div>
<div class="dialog_footer">
<div class="result">
优惠完:
<span class="i">¥</span> <span class="n">{{
formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount) }}</span>
</div>
<div class="btn">
<el-button @click="cancelAllDiscount">取消</el-button>
<el-button type="primary" @click="discountConfirm">确定</el-button>
</div>
</div>
</el-dialog>
<!-- 选择会员 -->
<SelectVipUser ref="SelectVipUserRef" @success="selectUserHandle" />
<!-- 选择优惠券 -->
<CouponModal ref="CouponModalRef" :orderList="props.orderList" @success="CouponModalSuccess" />
</template>
<script setup>
import _ from 'lodash'
import { onMounted, ref, reactive, watch } from "vue";
import { useUser } from "@/store/user.js";
import { clearNoNum, formatDecimal, inputFilterInt, inputFilterFloat } from "@/utils";
import { getPayType } from "@/api/account.js";
import scanModal from "@/components/payCard/scanModal.vue";
import SelectVipUser from '@/components/selectVipUser.vue'
import CouponModal from '@/components/payCard/couponModal.vue'
import { ElMessage } from "element-plus";
import { staffPermission } from "@/api/user.js";
import { cashPay, buyerPage, creditPay, vipPay } from "@/api/order.js";
import { calcUsablePoints } from '@/api/account.js'
import { useGoods } from "@/store/goods.js";
const emit = defineEmits(["paySuccess", 'orderExpired', 'reset']);
const SelectVipUserRef = ref(null)
const CouponModalRef = ref(null)
const goodsStore = useGoods();
const store = useUser();
const props = defineProps({
amount: {
type: Number,
default: 0,
},
selecttype: {
type: Number,
default: 0,
},
orderId: {
type: [Number, String],
default: 0,
},
orderList: {
type: Array,
default: []
},
isPrint: {
type: [Number, String],
default: 1,
}
});
const discountAmount = ref(null)
watch(props, () => {
if (goodsStore.cartInfo.costSummary) {
money.value = formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount)
}
// originOrderAmount.value = formatDecimal(props.amount - (goodsStore.tableInfo.tableFee || 0) -
// (goodsStore.cartInfo.packFee || 0))
})
const originOrderAmount = ref(0)
const money = ref("0");
const discountRateVlaue = ref(0)
const scanModalRef = ref(null);
const payActive = ref(0);
const payType = ref("");
const payList = ref([]);
const payLoading = ref(false);
const payData = ref({});
const roundAmount = ref(0) // 抹零金额
// 挂账人 start
const showBuyer = ref(false);
const buyerTable = reactive({
keywords: "",
loading: false,
page: 1,
size: 10,
total: 0,
list: [],
});
// 显示挂账人
function showBuyerHandle() {
showBuyer.value = true;
getBuyerList();
}
// 重置
function resetBuyerTable() {
buyerTable.keywords = "";
buyerTable.page = 1;
getBuyerList();
}
// 获取挂账人列表
async function getBuyerList() {
try {
buyerTable.loading = true;
const res = await buyerPage({
page: buyerTable.page,
size: buyerTable.size,
keywords: buyerTable.keywords,
status: 1,
responsiblePerson: "",
repaymentStatus: "",
});
buyerTable.list = res.records;
buyerTable.total = res.totalRow;
} catch (error) {
console.log(error);
}
buyerTable.loading = false;
}
// 选择挂账人支付
async function payCreditPayHandle(row) {
try {
payLoading.value = true;
buyerTable.loading = true;
payData.value.creditBuyerId = row.id
await creditPay(payData.value);
showBuyer.value = false;
payLoading.value = false;
buyerTable.loading = false;
emit("paySuccess");
} catch (error) {
console.log(error);
}
buyerTable.loading = false;
payLoading.value = false;
}
// 显示选择挂账人 end
// 获得扫码值
function scanCodeSuccess() {
emit("paySuccess");
}
// 会员支付
async function vipPayAjax(row) {
try {
// if (row.amount < money.value) {
// ElMessage.error('余额不足')
// return
// }
payData.value.payType = 'userPay'
payData.value.shopUserId = row.id
payData.value.checkOrderPay.userId = row.userId
payLoading.value = true;
await vipPay(payData.value)
goodsStore.showVipPrice = 0
goodsStore.vipUserInfo = ''
emit("paySuccess");
} catch (error) {
console.log(error);
if (error.code == 701) {
// 订单已过期需刷新购物车和订单
emit('orderExpired')
}
}
payLoading.value = false;
}
// 切换支付类型
async function payTypeChange(index, item) {
try {
console.log(item);
// await staffPermission('yun_xu_shou_kuan')
// if (item.disabled) return
payActive.value = index;
payType.value = item.payType;
upadatePayData()
if (item.payType == "scanCode") {
scanModalRef.value.show();
}
if (item.payType == "member-account") {
if (goodsStore.vipUserInfo.id) {
// await vipPayAjax(goodsStore.vipUserInfo)
} else {
SelectVipUserRef.value.show()
}
}
if (item.payType == "arrears") {
// 挂账支付
showBuyerHandle();
}
if (item.payType == 'deposit') {
scanModalRef.value.show();
}
} catch (error) {
console.log(error);
}
}
// 更新支付参数
function upadatePayData() {
// console.log(goodsStore.cartInfo);
payData.value.checkOrderPay = {
vipPrice: store.shopInfo.isMemberPrice ? goodsStore.showVipPrice : 0,
orderId: goodsStore.orderListInfo.id,
// discountRatio: (checkOrderPay.discount / 100).toFixed(2),
discountRatio: 0,
seatNum: goodsStore.allSelected ? 0 : goodsStore.tableInfo.num,
originAmount: goodsStore.cartInfo.costSummary.goodsRealAmount,
discountAmount: discountRateNumber.value,
productCouponDiscountAmount: goodsStore.cartInfo.costSummary.productCouponDeduction,
otherCouponDiscountAmount: goodsStore.cartInfo.costSummary.fullCouponDeduction,
orderAmount: goodsStore.cartInfo.costSummary.finalPayAmount, // 最终订单金额
roundAmount: 0,
pointsDiscountAmount: goodsStore.cartInfo.costSummary.pointDeductionAmount, //积分抵扣金额
pointsNum: goodsStore.cartInfo.costSummary.pointUsed,
discountActAmount: goodsStore.cartInfo.costSummary.fullReduction.actualAmount, // 满减活动金额
discountActId: goodsStore.cartInfo.costSummary.fullReduction.usedThreshold !== undefined ? goodsStore.cartInfo.costSummary.fullReduction.usedThreshold.activityId : '', // 满减活动id
couponList: couponResList1.value.map(item => item.id),
userId: goodsStore.vipUserInfo.userId || '',
allPack: goodsStore.allSelected,
limitRate: goodsStore.limitDiscountRes,
newCustomerDiscountId: goodsStore.newUserDiscount !== null ? goodsStore.newUserDiscount.id : goodsStore.newUserDiscount !== null ? goodsStore.newUserDiscount.id : '', // 新客立减Id
newCustomerDiscountAmount: goodsStore.newUserDiscount !== null ? goodsStore.newUserDiscount.amount : 0, // 新客立减金额
vipDiscountAmount: goodsStore.cartInfo.costSummary.vipDiscountAmount, // 超级会员折扣
}
}
// 结算支付
async function confirmOrder() {
try {
// 判断订单是否锁定
await goodsStore.isOrderLock({
table_code: goodsStore.orderListInfo.tableCode
})
if (payLoading.value) return
// await staffPermission("yun_xu_shou_kuan");
upadatePayData()
payType.value = payList.value[payActive.value].payType
if (payList.value[payActive.value].payType == "arrears") {
showBuyerHandle();
return
} else if (payList.value[payActive.value].payType == "scanCode") {
scanModalRef.value.show();
return
} else {
// if (money.value < props.amount) return
payLoading.value = true;
switch (payList.value[payActive.value].payType) {
case "deposit":
// 会员码支付
payLoading.value = false;
scanModalRef.value.show();
return;
case "cash":
//现金
if (props.selecttype == 0) {
payLoading.loading = true
await cashPay(payData.value);
}
break
case "member-account":
// 会员支付
payLoading.value = false;
if (goodsStore.vipUserInfo.id) {
await vipPayAjax(goodsStore.vipUserInfo)
} else {
SelectVipUserRef.value.show()
}
return;
default:
break;
}
payLoading.value = false;
emit("paySuccess");
}
} catch (error) {
console.log(error);
payLoading.value = false;
// if (error.code == 1003) {
// ElMessage.error(error.msg)
// return
// }
if (error.code == 701) {
// 订单已过期需刷新购物车和订单
emit('orderExpired')
}
scanModalRef.value.loading = false;
}
}
// 输入
function amountInput(num) {
if (discountAmount.value !== null) {
if (money.value + num <= discountAmount.value) {
money.value = clearNoNum({ value: (money.value += num) })
} else {
money.value = formatDecimal(+discountAmount.value);
}
roundAmount.value = formatDecimal(discountAmount.value - money.value)
} else {
if (money.value + num <= props.amount) {
money.value = clearNoNum({ value: (money.value += num) })
} else {
money.value = formatDecimal(+props.amount);
}
roundAmount.value = formatDecimal(props.amount - money.value)
}
payData.value.checkOrderPay.roundAmount = roundAmount.value;
payData.value.checkOrderPay.orderAmount = money.value;
}
// 删除
function delHandle() {
if (!money.value) return; money.value = money.value.substring(0, money.value.length - 1);
if (!money.value) {
money.value = "0";
}
if (discountAmount.value !== null) {
roundAmount.value = formatDecimal(discountAmount.value - money.value)
} else {
roundAmount.value = formatDecimal(props.amount - money.value)
}
payData.value.checkOrderPay.roundAmount = roundAmount.value;
payData.value.checkOrderPay.orderAmount = money.value;
}
/**
* 检查goodsStore.cartInfo.costSummary是否为真值的Promise
* @param {number} [interval=100] 轮询检查间隔(ms)
* @param {number} [timeout=5000] 超时时间(ms)超过则reject
* @returns {Promise} 当costSummary为真时resolve超时则reject
*/
function checkCostSummaryIsTruthy(interval = 100, timeout = 5000) {
return new Promise((resolve, reject) => {
// 记录开始时间,用于超时判断
const startTime = Date.now();
// 轮询检查函数
const check = () => {
const targetValue = goodsStore.cartInfo.costSummary;
// 检查是否为真值非null、undefined、0、''、false、NaN
if (targetValue) {
resolve(targetValue); // 成功时返回该值
return;
}
// 检查是否超时
if (Date.now() - startTime >= timeout) {
reject(new Error('检查超时costSummary始终为假值'));
return;
}
// 未满足条件且未超时,继续轮询
setTimeout(check, interval);
};
// 立即开始第一次检查
check();
});
}
// 获取支付方式
function queryPayTypeAjax() {
checkCostSummaryIsTruthy().then(async () => {
try {
const res = await getPayType();
res.map((item) => {
if (goodsStore.cartInfo.costSummary.finalPayAmount <= 0 && item.payType == "scanCode") {
item.disabled = true;
} else {
item.disabled = false;
}
});
payList.value = res.filter(item => item.isDisplay);
if (payList.value.length) {
if (payList.value[0].payType == "scanCode" && !payList.value[0].disabled) {
scanModalRef.value.show();
payType.value = payList.value[0].payType;
}
}
} catch (error) {
console.log(error);
}
})
}
/** 添加优惠 start */
const showCoupon = ref(false)
const couponFormRef = ref(null)
const couponFormUser = ref('')
const couponFormUserList = ref([])
const couponFormDiscountRate = ref(10)
const couponResType = ref('')
const couponResList1 = ref([])
const couponResList2 = ref([])
const pointOptions = ref({
// 新接口字段
enableRewards: 0, // 是否开启消费赠送积分1 开启)
consumeAmount: 0, // 每消费xx元赠送1积分
minPaymentAmount: 0, // 下单实付抵扣门槛(元)
maxDeductionRatio: 0, // 下单最高抵扣比例(如 0.2 表示最多抵扣 20%
equivalentPoints: 0, // 兼容字段1元 = ? 积分(保留)
pointsPerYuan: 0, // 积分数/元,例如 20 表示 20 积分 = 1 元
enablePointsMall: 0, // 是否开启积分商城
// 派生字段(页面使用)
minPoints: 0, // 最少使用积分(换算后)
maxPoints: 0, // 最大可用积分(换算后)
usable: true,
unusableReason: '',
amount: 0, // 抵扣金额(元)
potentialAmount: 0, // 用户当前积分可抵扣的最大金额(元,供展示)
loading: false
})
const couponForm = ref({
originAmount: 0,
discountRatio: "",
pointsNum: '',
amount: '',
coupon: '',
productCouponDiscountAmount: 0,
fullCouponDiscountAmount: 0,
couponList: []
})
const resetCouponForm = ref('')
// 清除已选择的用户
function clearCouponUser() {
couponFormUser.value = ''
couponFormUserList.value = []
goodsStore.clearVipUserInfo()
couponForm.value.pointsNum = ''
couponForm.value.discountRatio = ''
discountRateNumber.value = 0
couponResList1.value = []
updateCartCalc()
// resetCouponFormHandle()
}
// 折扣格式化
const discountRateNumber = ref(0)
const discountInput = _.debounce(function (e) {
// couponForm.value.amount = couponForm.value.originAmount
couponForm.value.discountRatio = inputFilterFloat(e)
if (couponForm.value.discountRatio > 9.9) {
couponForm.value.discountRatio = 9.9
}
if (couponForm.value.discountRatio < 0.1) {
couponForm.value.discountRatio = 0.1
}
if (couponForm.value.discountRatio) {
discountRateNumber.value = formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount - goodsStore.cartInfo.costSummary.finalPayAmount * (couponForm.value.discountRatio / couponFormDiscountRate.value))
} else {
discountRateNumber.value = formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount - goodsStore.cartInfo.costSummary.finalPayAmount)
}
updateCartCalc()
// // 将优惠券/积分所有设置初始化
// couponResList1.value = []
// couponResList2.value = []
// couponForm.value.pointsNum = ''
// pointOptions.value.amount = 0
}, 500)
// 取消所有优惠
function cancelAllDiscount() {
showCoupon.value = false
couponForm.value.pointsNum = ''
couponResList1.value = []
couponForm.value.discountRatio = ''
discountRateNumber.value = ''
updateCartCalc()
}
// 同意更新商品计算
function updateCartCalc() {
// 计算最大抵扣金额(按订单金额 * 最大抵扣比例)
const orderAmount = Number(goodsStore.cartInfo.costSummary.finalPayAmount || 0)
const maxDeductionAmount = (pointOptions.value.maxDeductionRatio && pointOptions.value.pointsPerYuan)
? orderAmount * pointOptions.value.maxDeductionRatio
: 0
goodsStore.calcCartInfo({
pointsPerYuan: pointOptions.value.pointsPerYuan,
maxDeductionAmount: maxDeductionAmount,
userPoints: couponForm.value.pointsNum,
backendCoupons: couponResList1.value,
fixedAmount: discountRateNumber.value
})
}
// 清除积分
function clearPoint() {
if (couponForm.value.pointsNum > 0) {
couponForm.value.pointsNum = ''
couponForm.value.amount = +couponForm.value.amount + +pointOptions.value.amount
pointOptions.value.amount = 0
}
updateCartCalc()
}
// 积分输入格式化
const pointInput = _.debounce(function (e) {
couponForm.value.pointsNum = inputFilterInt(e)
console.log('inputFilterInt===', couponForm.value.pointsNum);
// 未登录用户或无可用积分时直接归0并返回
const userAvailablePoints = Number(couponFormUser.value && couponFormUser.value.accountPoints ? couponFormUser.value.accountPoints : 0)
// 如果未登录、无可用积分或积分抵扣不可用不满足门槛或商家未开启直接归0并返回
if (!couponFormUser.value || !couponFormUser.value.id || userAvailablePoints <= 0 || !pointOptions.value.usable) {
couponForm.value.pointsNum = 0
pointOptions.value.amount = 0
updateCartCalc()
return
}
// 限制不得超过用户现有积分
if (couponForm.value.pointsNum > userAvailablePoints) {
couponForm.value.pointsNum = userAvailablePoints
}
// 边界限制:不得超过最大/小于最小
if (pointOptions.value.maxPoints && couponForm.value.pointsNum > pointOptions.value.maxPoints) {
couponForm.value.pointsNum = pointOptions.value.maxPoints
}
// 允许输入少于 minPoints 的积分(前端做友好展示),实际抵扣金额由 calcPointMoney 和后端规则决定
// 如果希望严格按照 minPoints 阈值生效可在此恢复置0逻辑
if (!e) {
couponForm.value.pointsNum = 0
couponForm.value.amount = +couponForm.value.amount + +pointOptions.value.amount
pointOptions.value.amount = 0
}
console.log('pointOptions.value===', pointOptions.value);
console.log('couponForm.value.pointsNum===', couponForm.value.pointsNum);
// 满足条件式开始计算抵扣金额,由后端返回
if (couponForm.value.pointsNum > 0 && (!pointOptions.value.maxPoints || couponForm.value.pointsNum <= pointOptions.value.maxPoints)) {
pointOptions.value.loading = true
calcPointMoney()
}
updateCartCalc()
}, 500)
// 根据积分计算可抵扣金额(前端计算,不依赖后端)
const calcPointMoney = async () => {
try {
const pointsPerYuan = Number(pointOptions.value.pointsPerYuan || 0) // 积分数/元,例如 20 表示 20 积分 = 1 元
const points = Number(couponForm.value.pointsNum || 0)
const orderAmount = Number(goodsStore.cartInfo.costSummary.finalPayAmount || 0)
// 若不满足计算条件,则直接退出并复位
if (!pointOptions.value.usable || !pointsPerYuan || points <= 0) {
pointOptions.value.amount = 0
couponForm.value.amount = couponForm.value.amount || originOrderAmount.value || orderAmount
updateCartCalc()
pointOptions.value.loading = false
return
}
// 积分可抵扣的金额(元) => 积分 / (积分数/元)
const deductionFromPoints = points / pointsPerYuan
// 最大可抵扣金额基于订单金额和最大抵扣比例(后端规则)
const maxDeductionAmount = orderAmount * (pointOptions.value.maxDeductionRatio || 0)
// 基数为当前可用于抵扣的订单金额可能已经包含其他优惠不能小于0
const baseAmount = Math.max(0, Number(couponForm.value.amount || originOrderAmount.value || orderAmount))
// 最终抵扣为:积分折算金额、最大可抵扣金额、当前可抵扣金额三者的最小值
let deduction = Math.min(deductionFromPoints, maxDeductionAmount, baseAmount)
deduction = Number(formatDecimal(deduction))
pointOptions.value.amount = deduction
couponForm.value.amount = formatDecimal(baseAmount - deduction)
updateCartCalc()
} catch (error) {
console.log(error);
}
pointOptions.value.loading = false
}
// 当dialog打开时
function couponDialogOpen() {
// couponResList2Amount.value = 0
// couponForm.value.discountRatio = ''
// // 需减去抹零金额
// couponForm.value.amount = formatDecimal(originOrderAmount.value - roundAmount.value)
// couponForm.value.originAmount = couponForm.value.amount
// resetCouponForm.value = { ...couponForm.value }
// couponResList1.value = []
// couponResList2.value = []
// 当购物车已存在用户时
if (goodsStore.vipUserInfo.id) {
couponFormUserList.value = [
{
id: goodsStore.vipUserInfo.userId,
nickName: goodsStore.vipUserInfo.nickName,
}
]
couponFormUser.value = goodsStore.vipUserInfo
pointOptionsAjax()
} else {
couponFormUserList.value = []
couponFormUser.value = ''
}
// 初始化优惠表单的金额基数:优先使用 originOrderAmount其次使用购物车的最终支付金额
const baseAmount = Number(originOrderAmount.value || 0) || Number(goodsStore.cartInfo.costSummary.finalPayAmount || 0)
couponForm.value.originAmount = formatDecimal(baseAmount)
couponForm.value.amount = formatDecimal(baseAmount - roundAmount.value)
resetCouponForm.value = { ...couponForm.value }
}
// 关闭后初始化dialog
function resetCouponFormHandle() {
couponForm.value = { ...resetCouponForm.value }
couponForm.value.pointsNum = ''
couponForm.value.discountRatio = ''
discountRateNumber.value = 0
// clearCouponUser()
cancelAllDiscount()
}
// 选择会员完成后
async function selectUserHandle(row) {
try {
console.log('selectUserHandle===', row);
goodsStore.selectUser(row)
// 选择会员后重新计算会员价
if (store.shopInfo.isMemberPrice && row.isVip) {
console.log('选择会员后重新计算会员价===', row);
goodsStore.showVipPrice = 1
} else {
goodsStore.showVipPrice = 0
}
couponResList1.value = []
goodsStore.calcCartInfo()
// emit('reset')
// couponForm.value.discountRatio = ''
// discountInput('')
if (showCoupon.value) {
setTimeout(() => {
couponDialogOpen()
}, 100)
couponFormUserList.value = [
{
id: row.userId,
nickName: row.nickName,
}
]
couponFormUser.value = row
pointOptionsAjax()
// 已存在选择的用户,并且切换了不通用户
if (couponFormUser.value && couponFormUser.value.id && row.userId != couponFormUser.value.userId) {
resetCoupon()
}
} else {
if (payList.value[payActive.value].payType == 'member-account') {
setTimeout(() => {
vipPayAjax(row)
}, 500)
}
}
} catch (error) {
console.log(error);
}
}
// 重选用户后重置优惠券和积分,并重新计算价格
function resetCoupon() {
couponResList1.value = []
couponResList2.value = []
couponForm.value.amount = originOrderAmount.value
}
// 选择完用户后开始获取积分使用配置
async function pointOptionsAjax() {
try {
const { pointsConfig, pointsUser } = await calcUsablePoints({
shopUserId: goodsStore.vipUserInfo.id,
// orderAmount: goodsStore.cartInfo.costSummary.finalPayAmount
})
const res = pointsConfig || {}
const userPoint = pointsUser
couponFormUser.value.accountPoints = userPoint.id ? userPoint.pointBalance : 0
// 映射最新接口字段
pointOptions.value.enableRewards = Number(res.enableRewards || 0)
pointOptions.value.consumeAmount = Number(res.consumeAmount || 0)
pointOptions.value.minPaymentAmount = Number(res.minPaymentAmount || 0)
pointOptions.value.maxDeductionRatio = Number(res.maxDeductionRatio || 0)
pointOptions.value.equivalentPoints = Number(res.equivalentPoints || 0)
pointOptions.value.enablePointsMall = Number(res.enablePointsMall || 0)
// 后端的 equivalentPoints 表示 积分数/元(例如 20 表示 20 积分 = 1 元)
pointOptions.value.pointsPerYuan = pointOptions.value.equivalentPoints
// 规范化 maxDeductionRatio后端可能以百分比(如50)返回若大于1则认为是百分比并除以100
if (pointOptions.value.maxDeductionRatio > 1) {
pointOptions.value.maxDeductionRatio = pointOptions.value.maxDeductionRatio / 100
}
// 计算页面需要的最小/最大可用积分(按接口返回的金额阈值与比例换算为积分)
const pointsPerYuan = pointOptions.value.pointsPerYuan || 0
const orderAmount = Number(goodsStore.cartInfo.costSummary.finalPayAmount || 0)
// 最少使用积分基于最小抵扣金额换算若无则为0minPaymentAmount 单位为元
pointOptions.value.minPoints = (pointOptions.value.minPaymentAmount && pointsPerYuan) ? Math.ceil(pointOptions.value.minPaymentAmount * pointsPerYuan) : 0
// 最大可用积分:基于订单金额与最大抵扣比例换算。后端返回的 maxDeductionRatio 表示可抵扣的最大金额占比,需换算为积分
const maxDeductionAmount = orderAmount * (pointOptions.value.maxDeductionRatio || 0)
pointOptions.value.maxPoints = (pointOptions.value.maxDeductionRatio && pointsPerYuan) ? Math.floor(maxDeductionAmount * pointsPerYuan) : 0
// 可用性:必须开启、订单满足最小支付金额门槛且最大可用积分>0
pointOptions.value.usable = (pointOptions.value.enableRewards === 1) && (orderAmount >= (pointOptions.value.minPaymentAmount || 0)) && pointOptions.value.maxPoints > 0
if (!pointOptions.value.usable) {
if (pointOptions.value.enableRewards !== 1) {
pointOptions.value.unusableReason = '商家未开启积分抵扣'
} else if (orderAmount < (pointOptions.value.minPaymentAmount || 0)) {
pointOptions.value.unusableReason = `订单金额不足¥${formatDecimal(+pointOptions.value.minPaymentAmount)},无法使用积分抵扣`
} else {
pointOptions.value.unusableReason = '订单不可抵扣积分'
}
} else {
pointOptions.value.unusableReason = ''
}
// 计算当前用户积分在当前订单下的潜在最大抵扣金额(仅供展示)
let potential = 0
const userPoints = Number(couponFormUser.value && couponFormUser.value.accountPoints ? couponFormUser.value.accountPoints : 0)
if (pointsPerYuan && userPoints > 0) {
potential = Math.min(userPoints / pointsPerYuan, maxDeductionAmount, orderAmount)
}
pointOptions.value.potentialAmount = formatDecimal(potential)
console.log('pointOptions after calc:', JSON.parse(JSON.stringify(pointOptions.value)), 'couponFormUser:', JSON.parse(JSON.stringify(couponFormUser.value)), 'orderAmount:', orderAmount)
} catch (error) {
console.log(error);
}
}
// 显示添加优惠
function showCouponHandle() {
showCoupon.value = true
}
// 显示选择优惠券
function showCounponModalHandle() {
CouponModalRef.value.show(couponFormUser.value.id)
}
const couponResList2Amount = ref(0)
// 选择完优惠券的回调事件
function CouponModalSuccess(res) {
couponResList1.value = res
updateCartCalc()
// couponResType.value = res.type
// clearPoint()
// if (res.type == 2) {
// couponResList2Amount.value = 0
// couponResList1.value = []
// couponForm.value.fullCouponDiscountAmount = 0
// couponResList2.value = res.couponList
// // 商品券
// const discountOrders = applyCoupons([...props.orderList], couponResList2.value)
// // 计算所有的优惠金额
// discountOrders.forEach(item => {
// if (item.isCoupon) {
// if (item.discount) {
// couponResList2Amount.value += item.discount
// }
// }
// })
// couponForm.value.productCouponDiscountAmount = couponResList2Amount.value
// }
// if (res.type == 1) {
// if (couponForm.value.amount < res.couponList[0].fullAmount) {
// ElMessage.error(`订单金额不足¥${formatDecimal(res.couponList[0].fullAmount, 2, true)},无法使用优惠券`)
// return
// }
// couponResList1.value = res.couponList
// couponForm.value.fullCouponDiscountAmount = res.couponList[0].discountAmount
// }
// let rate = 1
// if (couponForm.value.discountRatio) {
// rate = couponForm.value.discountRatio / couponFormDiscountRate.value
// }
// // 满减券切勿使用discount计算
// couponForm.value.amount = formatDecimal((originOrderAmount.value * rate) - (couponResList1.value.length ? couponResList1.value[0].discountAmount : 0) - couponResList2Amount.value)
// if (couponForm.value.amount < 0) {
// couponForm.value.amount = 0
// }
}
// 多个优惠券抵扣多个商品,优先已价格最低的抵扣
const applyCoupons = (orders, coupons) => {
// 遍历每张优惠券
coupons.forEach(coupon => {
// 筛选出订单列表中与当前优惠券 product_id 相同的商品
const eligibleProducts = orders.filter(order => order.product_id == coupon.proId);
// 对筛选出的商品按价格从小到大排序
eligibleProducts.sort((a, b) => a.price - b.price);
let couponUsed = false; // 用于标记优惠券是否已使用
// 依次使用优惠券抵扣价格最低的商品
eligibleProducts.forEach(product => {
if (!couponUsed && !product.isCoupon) {
product.isCoupon = true;
product.discount = coupon.discount
couponUsed = true; // 标记优惠券已使用
}
});
});
return orders;
};
// 删除优惠券
function delCoupon(index, t) {
if (t == 1) {
couponForm.value.amount = +couponForm.value.amount + +couponResList1.value[index].discountAmount
couponResList1.value.splice(index, 1)
} else {
// 恢复这个券的价格
const price = props.orderList.find(item => item.product_id == couponResList2.value[index].proId).price
couponForm.value.amount = +couponForm.value.amount + +price
couponResList2.value.splice(index, 1)
}
updateCartCalc()
console.log(couponForm.value.amount);
}
// 确认优惠
function discountConfirm() {
// 确认折扣
discountRateVlaue.value = couponForm.value.discountRatio || ''
// 计算折扣金额
if (couponForm.value.discountRatio) {
let rate = couponForm.value.discountRatio / couponFormDiscountRate.value
let discount = formatDecimal(couponForm.value.originAmount * rate)
payData.value.checkOrderPay.discountAmount = formatDecimal(couponForm.value.originAmount - discount)
} else {
payData.value.checkOrderPay.discountAmount = 0
}
if (couponFormUser.value.id) {
payData.value.checkOrderPay.userId = couponFormUser.value.userId
// 确认积分
payData.value.checkOrderPay.pointsNum = couponForm.value.pointsNum
payData.value.checkOrderPay.pointsDiscountAmount = pointOptions.value.amount
payData.value.checkOrderPay.productCouponDiscountAmount = couponForm.value.productCouponDiscountAmount
}
payData.value.checkOrderPay.orderAmount = couponForm.value.amount
// 优惠完之后加上餐位费
money.value = formatDecimal(+couponForm.value.amount + +(goodsStore.tableInfo.tableFee || 0) + +(goodsStore.cartInfo.packFee || 0))
discountAmount.value = money.value
payData.value.checkOrderPay.fullCouponDiscountAmount = couponForm.value.fullCouponDiscountAmount
showCoupon.value = false
payData.value.checkOrderPay.couponList = [...couponResList1.value.map(item => item.id), ...couponResList2.value.map(item => item.id)];
payData.value.checkOrderPay.orderAmount = money.value
}
/** 添加优惠 end */
// 初始化
async function payCardInit() {
console.log('payCardInit111111==============================', goodsStore.orderListInfo);
// const maxRetries = 10;
// let retries = 0;
// const waitForOrderId = async () => {
// while (retries < maxRetries && !goodsStore.orderListInfo.id) {
// await new Promise(resolve => setTimeout(resolve, 200));
// retries++;
// }
// return goodsStore.orderListInfo.id;
// };
// const orderId = await waitForOrderId();
// if (!orderId) {
// console.error('无法获取订单 ID');
// return;
// }
discountAmount.value = null
roundAmount.value = 0
discountRateVlaue.value = 0
money.value = `${formatDecimal(goodsStore.cartInfo.costSummary.finalPayAmount)}`;
originOrderAmount.value = formatDecimal(props.amount - (goodsStore.tableInfo.tableFee || 0) -
(goodsStore.cartInfo.packFee || 0))
payData.value = {
shopId: store.shopInfo.id,
buyerRemark: "", // 订单备注
checkOrderPay: {
// orderId: goodsStore.orderListInfo.id,
// vipPrice: store.shopInfo.isMemberPrice ? goodsStore.showVipPrice : 0, // 是否使用会员价
// allPack: goodsStore.allSelected, // 是否整单打包
// userId: goodsStore.vipUserInfo.userId || '',
// seatNum: goodsStore.tableInfo.num, // 用餐人数
// originAmount: formatDecimal(+goodsStore.cartInfo.totalAmount), // 订单原金额(包含打包费+餐位费) 不含折扣价格
// discountRatio: '', // 折扣比例(计算时 向上取整保留 两位小数)
// discountAmount: 0, // 手动优惠金额
// productCouponDiscountAmount: 0, // 商品优惠券抵扣金额
// fullCouponDiscountAmount: 0, // 满减优惠券抵扣金额
// couponList: [], // 用户使用的卡券
// orderAmount: formatDecimal(+goodsStore.cartInfo.totalAmount), // 订单金额
// roundAmount: 0, // 抹零金额 减免多少钱
// pointsDiscountAmount: 0, // 积分抵扣金额(tb_points_basic_setting表)
// pointsNum: 0, // 使用的积分数量 (扣除各类折扣 enable_deduction后使用)
// isPrint: 1
},
};
console.log('payData================================', payData.value);
if (!payList.value.length) {
queryPayTypeAjax()
}
}
defineExpose({
payCardInit,
resetCouponFormHandle
})
</script>
<style scoped lang="scss">
.point_tips {
&.err {
color: var(--el-color-danger);
}
}
.card {
padding: var(--el-font-size-base);
height: 100%;
}
.header {
padding-bottom: var(--el-font-size-base);
border-bottom: 1px solid #ececec;
display: flex;
.left {
flex: 1;
.t1 {
display: flex;
align-items: flex-end;
color: var(--el-color-danger);
font-weight: bold;
.title {
font-size: var(--el-font-size-base);
position: relative;
bottom: 6px;
}
.num {
font-size: 30px;
}
.clear {
position: relative;
bottom: 6px;
display: flex;
align-items: center;
margin-left: 20px;
}
}
.t2 {
display: flex;
color: #999;
gap: 10px;
padding-top: 10px;
span {
display: flex;
align-items: center;
}
}
}
}
.number_wrap {
padding: var(--el-font-size-base) 0;
.menus {
display: flex;
gap: var(--el-font-size-base);
.item {
height: 130px;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #efefef;
padding: 10px 0;
border-radius: 10px;
position: relative;
$lineHeight: 4px;
&.disabled {
filter: grayscale(1);
}
&.active {
&::after {
content: "";
width: 50%;
height: $lineHeight;
background-color: var(--primary-color);
position: absolute;
bottom: 0;
left: 25%;
border-radius: $lineHeight;
}
}
.img {
$size: 40px;
width: $size;
height: $size;
}
.title {
padding-top: 10px;
}
}
}
.input_wrap {
display: flex;
gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0;
.input {
display: flex;
align-items: center;
height: 60px;
border-radius: 6px;
border: 1px solid var(--primary-color);
font-size: calc(var(--el-font-size-base) + 6px);
padding: 0 var(--el-font-size-base);
}
}
.blance {
color: var(--el-color-danger);
font-size: calc(var(--el-font-size-base) + 10px);
}
}
.keybord_wrap {
display: flex;
.left {
--item-height: calc((100vh - 440px) / 4);
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: var(--item-height) var(--item-height) var(--item-height) var(--item-height);
gap: var(--el-font-size-base);
.item {
background-color: #efefef;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
font-size: calc(var(--el-font-size-base) + 10px);
&:active {
background-color: #dbdbdb;
}
}
}
.pay_btn {
flex: 0.3;
border-radius: 6px;
color: #fff;
background-color: var(--el-color-warning);
margin-left: var(--el-font-size-base);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(var(--el-font-size-base) + 10px);
}
}
.coupom_dialog {
height: 60vh;
overflow-y: auto;
.title {
font-size: 16px;
}
.flex {
display: flex;
align-items: center;
width: 100%;
gap: 10px;
.select_wrap {
flex: 1;
display: flex;
align-items: center;
}
}
.res {
display: flex;
align-items: center;
.i {
font-size: 14px;
position: relative;
top: 4px;
}
.n {
font-size: 24px;
font-weight: bold;
}
}
}
.dialog_footer {
display: flex;
align-items: center;
justify-content: flex-end;
justify-content: space-between;
padding-top: 20px;
.result {
font-size: 22px;
font-weight: bold;
}
.btn {
display: flex;
}
}
</style>