cashier-web/src/views/tool/Instead/components/order.vue

747 lines
22 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="order-box">
<div class="left">
<div class="user bg-fff u-p-20">
<div class="userinfo" v-if="user.id" @click="chooseUser">
<el-avatar class="avatar" :size="50" />
<div class="u-m-l-12">
<p>
<span class="name u-font-16">{{ user.nickName }}</span>
<span class="vip" v-if="user.isVip">VIP{{ user.isVip }}</span>
</p>
<p class="u-font-14 color-666 u-m-t-10">
<span class="money">余额:{{ user.amount }}</span>
<span class="score u-m-l-10">积分:{{ user.accountPoints }}</span>
</p>
</div>
</div>
<div class="userinfo" v-else @click="chooseUser">
<el-avatar class="avatar" :size="50" />
<div class="u-m-l-12 no-wrap">
<p>
<span class="name u-font-16">服务员下单</span>
</p>
<p class="u-font-14 color-666 u-m-t-10">
<span class="money">余额:</span>
<span class="score u-m-l-10">积分:</span>
</p>
</div>
</div>
<template v-if="user.id">
<div class="score">
<div class="u-flex u-col-center u-m-t-10">
<span class="u-font-14 font-bold u-m-r-20">积分抵扣</span>
<el-radio-group v-model="score.sel" size="small" class="">
<el-radio :value="-1">
<span class="u-font-14">不使用</span>
</el-radio>
<el-radio :value="0" :disabled="!pointsRes.usable">
<span class="u-font-14">使用</span>
</el-radio>
<!-- <el-radio :value="1" :disabled="!pointsRes.usable">
<span class="u-font-14">部分抵扣</span>
</el-radio> -->
</el-radio-group>
<el-input-number
class="u-m-l-10"
v-if="score.sel != -1"
v-model="usePointsNumber"
step-strictly
:step="pointsRes.equivalentPoints"
placeholder="请输入积分抵扣数量"
:min="pointsRes.minDeductionPoints"
:max="pointsRes.maxUsablePoints"
@change="pointsToMoney"
></el-input-number>
</div>
<p
class="u-font-14 color-666 u-m-t-10"
v-if="pointsRes.unusableReason && !pointsRes.usable"
>
<span class="color-red">*</span>
<span>{{ pointsRes.unusableReason }}</span>
</p>
<p class="u-font-14 color-666 u-m-t-10" v-else>
<span class="color-red">*</span>
<span>{{ pointsRes.equivalentPoints }}积分等于1元</span>
</p>
</div>
<div class="u-flex u-col-center u-m-t-20 no-wrap">
<span class="u-font-14 font-bold u-m-r-20">团购代金券</span>
<div class="u-flex my-select">
<span class="u-m-r-10">代金券名称</span>
<el-icon><ArrowDown /></el-icon>
</div>
<svg-icon iconClass="scan" size="30" class="u-m-l-10 cur-pointer"></svg-icon>
</div>
<div class="u-flex u-col-center u-m-t-20 no-wrap">
<span class="u-font-14 font-bold u-m-r-20">优惠券</span>
<div class="u-flex my-select" @click="openCoupon">
<span class="u-m-r-10">选择优惠券</span>
<el-icon><ArrowDown /></el-icon>
</div>
</div>
<div class="u-m-t-20" v-if="quansSelArr.length > 0">
<div class="font-bold u-m-b-10">已选优惠券</div>
<el-table empty-text="未选择优惠券" :data="quansSelArr">
<el-table-column type="index" width="50" label="#"></el-table-column>
<el-table-column prop="name" label="券名称"></el-table-column>
<el-table-column label="券类型" width="80">
<template v-slot="scope">
{{ UTILS.returnCoupType(scope.row.type) }}
</template>
</el-table-column>
<el-table-column label="商品信息">
<template v-slot="scope">
<div>
{{ scope.row.foods }}
</div>
</template>
</el-table-column>
<el-table-column prop="discountAmount" label="抵扣">
<template v-slot="scope">
<span class="color-red">{{ scope.row.discountAmount }}</span>
</template>
</el-table-column>
<el-table-column prop="useRestrictions" label="操作">
<template v-slot="scope">
<el-button type="danger" size="small" @click="delQuan(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</div>
<div class="u-m-t-30">
<el-button size="large" @click="discountShow">
{{ checkOrderPay.discount ? checkOrderPay.discount / 10 + "" : "整单打折/减免" }}
</el-button>
</div>
<div class="u-m-t-30">
<p class="u-font-16 font-bold u-m-r-20 font-bold u-flex">选择支付方式</p>
<div class="u-m-t-20">
<el-button
v-for="(item, index) in payTypes.list"
:key="index"
size="large"
:type="index == payTypes.sel ? 'primary' : ''"
:disabled="canUsePayType(item)"
@click="changePayType(index)"
>
{{ item.payName }}
</el-button>
</div>
<div class="u-m-t-20">
<el-button type="primary" size="large" @click="nowPayClick()">立即支付</el-button>
</div>
</div>
</div>
<div class="right">
<h3>账单明细</h3>
<p class="u-font-12 u-m-b-20">
<span class="color-red">*</span>
<span class="color-red">餐位费和打包费不参与折扣和满减</span>
</p>
<div class="order-info">
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">订单号:</span>
<span class="u-m-l-10 value">{{ orderInfo.orderNo }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">餐位费</span>
<span class="u-m-l-10 value">¥{{ carts.orderCostSummary.seatFee }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">打包费</span>
<span class="u-m-l-10 value">¥{{ carts.orderCostSummary.packFee }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">商品订单金额</span>
<span class="u-m-l-10 value">
¥{{
carts.orderCostSummary.goodsOriginalAmount -
carts.orderCostSummary.goodsDiscountAmount
}}
</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">商品优惠券</span>
<span class="u-m-l-10 value">-¥{{ productCouponDiscountAmount }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">其他优惠券</span>
<span class="u-m-l-10 value">-¥{{ fullCouponDiscountAmount }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">整单改价</span>
<span class="u-m-l-10 value color-red">
-¥{{ carts.orderCostSummary.merchantReduction.actualAmount }}
</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">积分抵扣</span>
<span class="u-m-l-10 value">-¥{{ carts.orderCostSummary.pointDeductionAmount }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">抹零</span>
<span class="u-m-l-10 value">-¥{{ 0 }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">总价(商品订单金额-商品优惠券-满减券-整单改价-积分抵扣)</span>
<span class="u-m-l-10 value color-red">¥{{ totalPrice }}</span>
</div>
<div class="u-flex u-m-b-10 u-row-between">
<span class="title">应付金额(总价+客座费+打包费)</span>
<span class="u-m-l-10 value price">¥{{ carts.orderCostSummary.finalPayAmount }}</span>
</div>
</div>
</div>
<!-- 扫码 -->
<scanPay
ref="refScanPay"
:order="orderInfo"
@confirm="refScanPayConfirm"
@paysuccess="paysuccess"
></scanPay>
<!-- 打折 -->
<discount ref="refDiscount" @confirm="discountConfirm"></discount>
<!-- 优惠券 -->
<popup-coupon ref="refCoupon" :user="user" @confirm="refCouponConfirm"></popup-coupon>
<!-- 挂账 -->
<chooseGuaZahng
ref="refGuaZhang"
:payMoney="currentpayMoney"
@confirm="refGuaZhangConfirm"
></chooseGuaZahng>
</div>
</template>
<script setup>
import * as UTILS from "@/utils/coupon-utils.js";
import * as quanUtil from "../quan_util.js";
import { OrderPriceCalculator } from "@/utils/goods";
import { useCartsStore } from "@/store/modules/carts";
import { useUserStore } from "@/store/modules/user";
import chooseGuaZahng from "./popup-choose-guazhang.vue";
const shopUser = useUserStore();
const carts = useCartsStore();
import popupCoupon from "./popup-coupon.vue";
import PointsApi from "@/api/account/points";
import payTypeApi from "@/api/account/payType";
import payApi from "@/api/order/pay";
import scanPay from "./scan-pay.vue";
import discount from "./discount.vue";
import { ElLoading } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus";
import { BigNumber } from "bignumber.js";
// 配置BigNumber精度
BigNumber.set({
DECIMAL_PLACES: 2,
ROUNDING_MODE: BigNumber.ROUND_DOWN, // 向下取整,符合业务需求
});
//挂账
const refGuaZhang = ref();
function refGuaZhangConfirm(guazhangRen) {
payOrder("arrears", true, guazhangRen);
}
function refGuaZhangShow() {
refGuaZhang.value.open();
}
const refCoupon = ref();
let quansSelArr = ref([]);
function openCoupon() {
//商品订单金额
const price = new BigNumber(carts.orderCostSummary.goodsOriginalAmount)
.minus(carts.orderCostSummary.goodsDiscountAmount)
.toFixed(2);
refCoupon.value.open(price, props.orderInfo);
}
function refCouponConfirm(e, goodsList) {
e = e.map((v) => {
return {
...v,
couponType: v.type,
title: v.name,
};
});
console.log("refCouponConfirm", e);
carts.setCoupons(e);
quansSelArr.value = e;
checkOrderPay.discountAmount = 0;
checkOrderPay.discount = 0;
}
function delQuan(row) {
const index = quansSelArr.value.findIndex((v) => v.id == row.id);
if (index != -1) {
quansSelArr.value.splice(index, 1);
}
carts.setCoupons(quansSelArr.value);
}
//打折
const refDiscount = ref();
const checkOrderPay = reactive({
discountAmount: 0, //手动优惠金额
discount: 0,
});
function discountConfirm(e) {
console.log(e);
Object.assign(checkOrderPay, e);
if (e.discount) {
carts.setMerchantDiscountReduction(e.discount);
} else {
carts.setMerchantFixedReduction(e.discountAmount);
checkOrderPay.discount = 0;
}
score.sel = -1;
usePointsNumber.value = 0;
}
// 计算商家减免前金额使用bignumber.js确保精度
function returnMerchantReductionDiscount() {
const {
goodsOriginalAmount, // 商品原价总和
goodsDiscountAmount, // 商品折扣
couponDeductionAmount, // 优惠券抵扣
pointDeductionAmount, // 积分抵扣
seatFee, // 餐位费(不参与减免)
packFee, // 打包费(不参与减免)
additionalFee,
} = carts.orderCostSummary;
// 将所有金额转换为BigNumber实例
const originalAmount = new BigNumber(goodsOriginalAmount);
const discountAmount = new BigNumber(goodsDiscountAmount);
const couponAmount = new BigNumber(couponDeductionAmount);
const pointAmount = new BigNumber(pointDeductionAmount);
const seat = new BigNumber(seatFee);
const pack = new BigNumber(packFee);
const additional = new BigNumber(additionalFee);
// 按照原逻辑进行精确计算
const total = originalAmount
.minus(discountAmount) // 减去商品折扣
.minus(couponAmount) // 减去优惠券抵扣
.minus(pointAmount) // 减去积分抵扣
.plus(seat) // 加上餐位费
.plus(pack) // 加上打包费
.plus(additional); // 加上附加费
// 确保结果不小于0返回数字类型
return total.lte(0) ? 0 : total.toNumber();
}
function discountShow(e) {
refDiscount.value.open({
// amount: carts.goodsTotal - productCouponDiscountAmount.value,
amount: returnMerchantReductionDiscount(),
});
}
const props = defineProps({
table: {
type: Object,
default: () => {},
},
user: {
type: Object,
default: () => {
return { id: "" };
},
},
perpole: {
type: [Number, String],
default: 0,
},
orderInfo: {
type: Object,
default: () => {},
},
});
const seatAmount = computed(() => {
if (shopUser.userInfo.isTableFee) {
return "0.00";
}
if (props.perpole >= 1) {
return (props.perpole * shopUser.userInfo.tableFee).toFixed(2);
}
return "0.00";
});
watch(
() => props.user.id,
(newval) => {
quansSelArr.value = [];
checkOrderPay.discountAmount = 0;
checkOrderPay.discount = 0;
score.sel = -1;
usePointsNumber.value = 0;
if (newval !== "") {
pointsInit();
}
}
);
watch(
() => props.orderInfo.id,
(newval) => {
if (newval !== "") {
pointsInit();
}
}
);
//002-获取订单可用积分及抵扣金额(支付页面使用)
const pointsRes = ref({ usable: true, maxUsablePoints: 0, minDeductionPoints: 0 });
const usePointsNumber = ref(0);
//积分可抵扣最大金额
const scoreMaxMoney = computed(() => {
return (
carts.orderCostSummary.finalPayAmount +
carts.orderCostSummary.pointDeductionAmount -
carts.orderCostSummary.merchantReduction.actualAmount
);
});
watch(
() => scoreMaxMoney.value,
(newval, oldval) => {
if (newval != oldval) {
pointsInit();
}
}
);
async function pointsInit() {
if (!props.user.id) {
return;
}
const res = await PointsApi.calcOrderUsablePoints({
shopUserId: props.user.id,
orderAmount: scoreMaxMoney.value,
});
pointsRes.value = res;
carts.pointDeductionRule.pointsPerYuan = res.equivalentPoints;
if (score.sel == -1) {
return;
}
usePointsNumber.value = res.usable ? res.maxUsablePoints : 0;
if (res.usable) {
} else {
score.sel = -1;
}
return res;
}
const emits = defineEmits(["chooseUser", "paysuccess"]);
function chooseUser() {
emits("chooseUser");
}
const score = reactive({
list: [],
sel: -1,
});
const payTypes = reactive({
list: [],
sel: 0,
});
watch(
() => score.sel,
(newval) => {
console.log(newval);
usePointsNumber.value = 0;
pointsInit();
}
);
watch(
() => usePointsNumber.value,
(newval) => {
carts.userPoints = newval;
}
);
function canUsePayType(item) {
if (currentpayMoney.value * 1 == 0) {
return item.payType == "cash" ? false : true;
}
// if (item.payType == "virtual" && item.payName == "会员支付") {
// return props.user.id ? false : true;
// }
return false;
}
const refScanPay = ref();
function changePayType(i) {
const payType = payTypes.list[i].payType;
payTypes.sel = i;
if (payType == "member-account") {
return chooseUser();
}
refScanPayOpen(payType);
}
function returnPayParams() {
console.log("carts.orderCostSummary", carts.orderCostSummary);
return {
money: currentpayMoney.value * 1,
shopId: localStorage.getItem("shopId"),
authCode: authCode,
checkOrderPay: {
vipPrice: shopUser.userInfo.isMemberPrice && props.user.id && props.user.isVip ? 1 : 0,
orderId: props.orderInfo.id,
// discountRatio: (checkOrderPay.discount / 100).toFixed(2),
discountRatio: 0,
seatNum: carts.dinnerType == "dine-in" ? props.perpole * 1 : 0,
originAmount: carts.orderCostSummary.goodsRealAmount,
discountAmount: discountAmount.value,
productCouponDiscountAmount: carts.orderCostSummary.productCouponDeduction,
otherCouponDiscountAmount: carts.orderCostSummary.fullCouponDeduction,
orderAmount: carts.orderCostSummary.finalPayAmount, // 最终订单金额
roundAmount: props.orderInfo.roundAmount,
pointsDiscountAmount: carts.orderCostSummary.pointDeductionAmount, //积分抵扣金额
pointsNum: carts.orderCostSummary.pointUsed,
couponList: carts.coupons.map((v) => v.id),
userId: props.user.userId || "",
allPack: carts.dinnerType == "take-out" ? 1 : 0,
},
};
}
function refScanPayOpen(payType) {
if (payType == "member-account") {
chooseUser();
return;
}
if (payType == "deposit") {
return refScanPay.value.open(returnPayParams(), "deposit");
}
if (payType == "scanCode") {
return refScanPay.value.open(returnPayParams(), "scanCode");
}
if (payType == "arrears") {
refGuaZhangShow();
return;
}
}
async function getPaytype() {
const res = await payTypeApi.getList();
payTypes.list = res.filter((v) => v.isDisplay);
if (currentpayMoney.value * 1 <= 0) {
payTypes.sel = payTypes.list.findIndex((v) => v.payType == "cash");
}
}
function nowPayClick(payType) {
payType = payType || payTypes.list[payTypes.sel].payType;
if (payType === "cash") {
ElMessageBox.confirm("是否确认已现金收款:" + currentpayMoney.value + "元", "快捷支付", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
payOrder("cash");
})
.catch(() => {});
return;
}
if (payType == "member-account") {
if (!props.user.id) {
return ElMessage.error("请先选择会员");
}
payOrder(payType);
return;
}
refScanPayOpen(payType);
}
let authCode = "";
function refScanPayConfirm($authCode, isScan) {
const payType = payTypes.list[payTypes.sel].payType;
authCode = $authCode;
payOrder(payType, isScan);
}
let payTimer = null;
//是否是正扫
async function payOrder(payType, isScan, guazhangren) {
clearTimeout(payTimer);
const loading = ElLoading.service({
lock: true,
text: "支付中,请稍等……",
background: "rgba(0, 0, 0, 0.7)",
});
payTimer = setTimeout(() => {
ElMessage.error("支付超时");
loading.close();
}, 1000 * 20);
let res = undefined;
console.log(payType);
try {
if (payType == "scanCode") {
res = isScan
? await payApi.scanPay(returnPayParams())
: await payApi.microPay(returnPayParams());
}
if (payType == "cash") {
res = await payApi.cashPay(returnPayParams());
}
if (payType == "deposit") {
res = await payApi.vipPay({ ...returnPayParams(), payType: "scanCode" });
}
if (payType == "member-account") {
res = await payApi.vipPay({
...returnPayParams(),
payType: "userPay",
shopUserId: props.user.id,
});
}
if (payType == "arrears") {
res = await payApi.creditPay({ ...returnPayParams(), creditBuyerId: guazhangren.id });
}
carts.clear();
} catch (error) {
console.log(error);
clearTimeout(payTimer);
loading.close();
}
if (res) {
loading.close();
paysuccess();
}
}
function paysuccess() {
clearTimeout(payTimer);
ElMessage.success("支付成功");
emits("paysuccess");
}
//整单改价
const discountAmount = computed(() => {
const money = carts.goodsTotal - productCouponDiscountAmount.value;
if (checkOrderPay.discount) {
return ((money * (100 - checkOrderPay.discount)) / 100).toFixed(2);
}
return checkOrderPay.discountAmount;
});
//满减优惠券
const fullCouponDiscountAmount = computed(() => {
return (
carts.orderCostSummary.fullCouponDeduction || props.orderInfo.fullCouponDiscountAmount || 0
);
});
//商品券抵扣金额
const productCouponDiscountAmount = computed(() => {
// 优先从 Store 扩展字段取,若无则用 props 数据(过渡方案)
return carts.orderCostSummary.productCouponDeduction;
});
const totalPrice = computed(() => {
// 使用bignumber.js处理高精度计算
const originalAmount = new BigNumber(carts.orderCostSummary.goodsOriginalAmount);
const discountAmount = new BigNumber(carts.orderCostSummary.goodsDiscountAmount);
const couponDeduction = new BigNumber(carts.orderCostSummary.couponDeductionAmount);
const merchantReduction = new BigNumber(carts.orderCostSummary.merchantReduction.actualAmount);
const pointDeduction = new BigNumber(carts.orderCostSummary.pointDeductionAmount);
// 逐步计算:原价 - 商品折扣 - 优惠券 - 商家减免 - 积分抵扣
const total = originalAmount
.minus(discountAmount)
.minus(couponDeduction)
.minus(merchantReduction)
.minus(pointDeduction)
.decimalPlaces(2, BigNumber.ROUND_DOWN); // 保持与工具库一致的舍入策略
const n = total.toNumber();
if (n <= 0) {
return 0;
}
return total.toNumber();
});
//应付金额
const currentpayMoney = computed(() => {
return carts.orderCostSummary.finalPayAmount;
// return (totalMoney.value * 1 + carts.packFee * 1 + seatAmount.value * 1).toFixed(2);
});
watch(
() => currentpayMoney.value,
(newval) => {
if (newval * 1 <= 0) {
payTypes.sel = payTypes.list.findIndex((v) => v.payType == "cash");
}
}
);
onMounted(() => {
carts.payParamsInit();
getPaytype();
});
defineExpose({
nowPayClick,
});
</script>
<style lang="scss" scoped>
.userinfo {
display: flex;
align-items: center;
line-height: 1;
cursor: pointer;
}
.vip {
padding: 2px 5px;
background: #f7793d;
color: #fff;
border-radius: 4px;
margin-left: 10px;
font-size: 10px;
}
.order-box {
display: flex;
padding: 20px 20px;
background-color: #f7f7fa;
min-height: 100%;
.left,
.right {
flex: 1;
}
.left {
padding-right: 20px;
}
.right {
border-left: 1px solid #ebebeb;
padding-left: 20px;
.order-info {
font-size: 14px;
.title {
}
.value {
}
.price {
color: #fa5555;
font-size: 20px;
}
}
}
}
.my-select {
border: 1px solid #d9d9d9;
border-radius: 4px;
margin-left: 12px;
cursor: pointer;
font-size: 14px;
color: #666;
padding: 8px 10px 8px 20px;
}
</style>