cashier-web/src/store/modules/carts.ts

810 lines
25 KiB
TypeScript
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.

import { store } from "@/store";
import WebSocketManager, { type ApifoxModel, msgType } from "@/utils/websocket";
import orderApi from "@/api/order/order";
import { useUserStoreHook } from "@/store/modules/user";
import productApi from "@/api/product/index";
import * as UTILS from "@/utils/coupon-utils.js";
import { BigNumber } from "bignumber.js";
import _ from "lodash";
// 导入工具库及相关类型
import {
OrderPriceCalculator,
BaseCartItem,
BackendCoupon,
ActivityConfig,
OrderExtraConfig, MerchantReductionConfig, MerchantReductionType,
GoodsType
} from "@/utils/goods";
const shopUser = useUserStoreHook();
export interface CartsState {
id: string | number;
[property: string]: any;
}
interface SeatFeeConfig {
pricePerPerson: number;
personCount: number;
isEnabled: boolean;
}
interface PointDeductionRule {
pointsPerYuan: number;
maxDeductionAmount: number;
}
export const useCartsStore = defineStore("carts", () => {
// ------------------------------ 1. 移到内部的工具函数(核心修复) ------------------------------
// 适配工具库 BaseCartItem 接口的商品数据转换函数(原外部函数,现在内部)
const convertToBaseCartItem = (item: any): BaseCartItem => {
const skuData = item.skuData
? {
id: item.skuData.id || item.sku_id,
salePrice: item.skuData.salePrice || 0,
memberPrice: item.skuData.memberPrice || 0
}
: undefined;
let productType: GoodsType = GoodsType.NORMAL;
switch (item.product_type) {
case 'weight': productType = GoodsType.WEIGHT; break;
case 'gift': productType = GoodsType.GIFT; break;
case 'package': productType = GoodsType.PACKAGE; break;
default: productType = GoodsType.NORMAL;
}
return {
id: item.id,
product_id: item.product_id,
salePrice: item.salePrice || 0,
number: item.number || 0,
product_type: productType,
is_temporary: !!item.is_temporary,
is_gift: !!item.is_gift,
returnNum: item.returnNum || 0,
memberPrice: item.memberPrice || 0,
discountSaleAmount: item.discount_sale_amount || 0,
packFee: item.packFee || 0,
packNumber: item.pack_number || 0,
activityInfo: item.activityInfo
? {
type: item.activityInfo.type,
discountRate: OrderPriceCalculator.formatDiscountRate(item.activityInfo.discountRate),
vipPriceShare: !!item.activityInfo.vipPriceShare
}
: undefined,
skuData
};
};
// 合并所有商品列表(原外部函数,现在内部,可直接访问 list/giftList/oldOrder
const getAllGoodsList = (): BaseCartItem[] => {
const currentGoods = (list.value || []).map(convertToBaseCartItem);
const giftGoods = (giftList.value || []).map(convertToBaseCartItem);
// 扁平化历史订单商品
const oldOrderGoods = Object.values(oldOrder.value.detailMap || {})
.flat()
.map(convertToBaseCartItem);
return [...currentGoods, ...giftGoods, ...oldOrderGoods];
};
// ------------------------------ 2. Store 内部原有响应式变量 ------------------------------
// 选择用户
const vipUser = ref<{ id?: string | number, isVip?: boolean }>({});
function changeUser(user: any) {
vipUser.value = user;
userPoints.value = 0;
}
// 就餐类型
let dinnerType = ref<'dine-in' | 'take-out'>('dine-in');
// 是否启用会员价
const useVipPrice = computed(() => {
return (shopUser.userInfo.isMemberPrice && vipUser.value.id && vipUser.value.isVip) ? true : false;
});
// 台桌id
const table_code = ref('');
// 购物车是否初始化连接加载完成
const isLinkFinshed = ref(false);
// 当前购物车数据(现在 getAllGoodsList 能直接访问)
const list = useStorage<any[]>("carts", []);
// 历史订单数据(现在 getAllGoodsList 能直接访问)
const oldOrder = useStorage<any>("Instead_olold_order", {
detailMap: [],
originAmount: 0
});
// 代客下单页面商品缓存
const goods = useStorage<any[]>("Instead_goods", []);
async function getGoods(query: any) {
const res = await productApi.getPage({
page: 1,
size: 999,
status: "on_sale",
...query,
});
goods.value = res.records;
setGoodsMap(goods.value);
}
function setGoodsMap(goods: any[]) {
for (let item of goods) {
goodsMap[item.id] = item;
}
}
// 赠菜(现在 getAllGoodsList 能直接访问)
const giftList = useStorage<any[]>("giftList", []);
let goodsMap: { [key: string]: any } = useStorage('Instead_goods_map', {});
// ------------------------------ 3. 原有计算属性和业务逻辑(无需修改,正常调用 getAllGoodsList ------------------------------
// 当前选中cart相关状态
let selListIndex = ref(-1);
const isSelGift = ref(false);
const isOldOrder = ref(false);
const selPlaceNum = ref(-1);
const defaultCart = { id: '', number: 0 };
// 当前购物车是否为空
const isEmpty = computed(() => {
return list.value.length === 0 && giftList.value.length === 0
});
// 当前购物车选中数据
const selCart = computed(() => {
if (isOldOrder.value && selPlaceNum.value >= 0) {
return oldOrder.value.detailMap[selPlaceNum.value][selListIndex.value] || defaultCart;
}
if (isSelGift.value) {
return giftList.value[selListIndex.value] || defaultCart;
}
return list.value[selListIndex.value] || defaultCart;
});
// 当前购物车选中对应商品数据
const selGoods = computed(() => {
return goodsMap[selCart.value.product_id];
});
// 当前选中购物车数据是否是可选套餐商品
const isCanSelectGroup = computed(() => {
if (!selGoods.value) return false;
return selGoods.value.type === 'package' && selGoods.value.groupType === 1;
});
// 初始配置:默认无减免(固定金额 0 元)
const merchantReductionConfig = ref<MerchantReductionConfig>({
type: MerchantReductionType.FIXED_AMOUNT,
fixedAmount: 0
});
// 暴露外部修改方法:设置固定金额减免
function setMerchantFixedReduction(amount: number) {
merchantReductionConfig.value = {
type: MerchantReductionType.FIXED_AMOUNT,
fixedAmount: Math.max(0, amount) // 确保金额非负
};
}
// 暴露外部修改方法:设置比例折扣减免(传入折扣率,如 90 代表 9 折)
function setMerchantDiscountReduction(discountRate: number) {
merchantReductionConfig.value = {
type: MerchantReductionType.DISCOUNT_RATE,
discountRate: Math.max(0, Math.min(100, discountRate)) // 确保折扣率在 0-100% 之间
};
}
// 暴露外部方法:清空商家减免
function clearMerchantReduction() {
merchantReductionConfig.value = {
type: MerchantReductionType.FIXED_AMOUNT,
fixedAmount: 0
};
}
const seatFeeConfig = ref<SeatFeeConfig>({
pricePerPerson: shopUser.userInfo.tableFee || 0,
personCount: 0,
isEnabled: !shopUser.userInfo.isTableFee
})
const pointDeductionRule = ref<PointDeductionRule>({
pointsPerYuan: 100,
maxDeductionAmount: Infinity
})
//使用积分数量
const userPoints = ref(0);
// 订单额外配置(现在依赖响应式的 merchantReduction
const orderExtraConfig = computed<OrderExtraConfig>(() => ({
// 引用扩展后的商家减免配置
merchantReduction: merchantReductionConfig.value,
additionalFee: 0,
pointDeductionRule: pointDeductionRule.value,
seatFeeConfig: seatFeeConfig.value,
currentStoreId: shopUser.userInfo.shopId?.toString() || '',
userPoints: userPoints.value,
isMember: useVipPrice.value,
memberDiscountRate: shopUser.userInfo.memberDiscountRate || 1
}));
// 营销活动列表
const activityList = computed<ActivityConfig[]>(() => {
return [];
});
function setCoupons(cps: BackendCoupon[]) {
console.log('setCoupons', cps);
let goodsCoupon = cps.filter((v) => v.type == 2);
let otherCoupon = cps.filter((v) => v.type != 2);
const canDikouGoodsArr = UTILS.returnCanDikouGoods(allGoods.value, [], vipUser.value);
//商品订单金额
const goodsOrderPrice = new BigNumber(orderCostSummary.value.goodsOriginalAmount)
.minus(orderCostSummary.value.goodsDiscountAmount)
.toFixed(2);
goodsCoupon = goodsCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(canDikouGoodsArr, v, vipUser.value, goodsOrderPrice, [], shopUser.userInfo);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount
};
});
otherCoupon = otherCoupon.map((v) => {
const discount = UTILS.returnCouponDiscount(canDikouGoodsArr, v, vipUser.value, goodsOrderPrice, goodsCoupon, shopUser.userInfo);
return {
...v,
discount,
discountAmount: discount ? discount.discountPrice : v.discountAmount
};
});
coupons.value = cps;
}
// 优惠券列表
const coupons = ref<BackendCoupon[]>([]);
// 商品加入购物车顺序
const cartOrder = ref<Record<string, number>>({});
let allGoods = ref<BaseCartItem[]>([]);
// 订单费用汇总(调用内部的 getAllGoodsList
const orderCostSummary = computed(() => {
allGoods.value = getAllGoodsList();
const costSummary = OrderPriceCalculator.calculateOrderCostSummary(
allGoods.value,
dinnerType.value,
coupons.value,
activityList.value,
orderExtraConfig.value,
cartOrder.value,
new Date()
);
return costSummary;
});
// 赠菜总价(调用内部的 getAllGoodsList
const giftMoney = computed(() => {
const allGoods = getAllGoodsList();
const giftGoods = allGoods.filter(OrderPriceCalculator.isGiftGoods);
return OrderPriceCalculator.calcGoodsOriginalAmount(giftGoods);
});
// 打包数量(调用内部的 getAllGoodsList
const packNum = computed(() => {
const allGoods = getAllGoodsList();
return allGoods.reduce((total, goods) => {
const packNumber = OrderPriceCalculator.isWeightGoods(goods)
? 1
: (dinnerType.value === 'take-out' ? goods.number : goods.packNumber || 0);
return total + Math.max(0, packNumber);
}, 0);
});
// 打包费
const packFee = computed(() => {
return orderCostSummary.value.packFee;
});
// 会员优惠(调用内部的 getAllGoodsList
const vipDiscount = computed(() => {
const allGoods = getAllGoodsList();
const nonMemberRealAmount = OrderPriceCalculator.calcGoodsRealAmount(
allGoods,
{ isMember: false, memberDiscountRate: 1 },
activityList.value
);
return OrderPriceCalculator.truncateToTwoDecimals(
nonMemberRealAmount - orderCostSummary.value.goodsOriginalAmount
);
});
// 单品改价优惠(调用内部的 getAllGoodsList
const singleDiscount = computed(() => {
const allGoods = getAllGoodsList();
const noDiscountGoods = allGoods.map(goods => ({ ...goods, discountSaleAmount: 0 }));
const noDiscountRealAmount = OrderPriceCalculator.calcGoodsRealAmount(
noDiscountGoods,
{ isMember: orderExtraConfig.value.isMember, memberDiscountRate: orderExtraConfig.value.memberDiscountRate },
activityList.value
);
return OrderPriceCalculator.truncateToTwoDecimals(
noDiscountRealAmount - orderCostSummary.value.goodsOriginalAmount
);
});
// 已优惠文案
const yiyouhui = computed(() => {
const totalDiscount = giftMoney.value + vipDiscount.value + singleDiscount.value;
return totalDiscount > 0
? `已优惠¥${OrderPriceCalculator.truncateToTwoDecimals(totalDiscount).toFixed(2)}`
: '';
});
// 只算商品的总价
const goodsTotal = computed(() => {
return orderCostSummary.value.goodsOriginalAmount.toFixed(2);
});
// 总计数量(调用内部的 getAllGoodsList
const totalNumber = computed(() => {
const allGoods = getAllGoodsList();
return allGoods.reduce((total, goods) => {
return total + Math.max(0, goods.number - (goods.returnNum || 0));
}, 0);
});
// 支付总价
const payMoney = computed(() => {
return orderCostSummary.value.finalPayAmount.toFixed(2);
});
// ------------------------------ 4. 原有业务逻辑增删改查、WebSocket等保持不变 ------------------------------
function changeNumber(step: number, item: CartsState) {
if (item.number * 1 + step <= 0) {
del(item);
return;
}
const newNumber = item.number * 1 + step * 1;
let pack_number = newNumber < item.pack_number ? (item.pack_number * 1 + step * 1) : item.pack_number;
if (dinnerType.value === 'take-out') {
pack_number = newNumber;
}
if (item.product_type === 'weight') {
pack_number = 1;
}
update({ ...item, number: newNumber, pack_number });
}
function changeSelCart(cart: CartsState) {
console.log(cart)
if (!cart.id) {
return
}
if (cart.placeNum) {
selPlaceNum.value = cart.placeNum
isOldOrder.value = true
console.log(oldOrder.value.detailMap[cart.placeNum])
selListIndex.value = oldOrder.value.detailMap[cart.placeNum].findIndex((item: CartsState) => {
return item.id == cart.id
})
return
}
if (cart.is_gift) {
isSelGift.value = true
} else {
isSelGift.value = false
}
if (cart.is_gift) {
isSelGift.value = true
selListIndex.value = giftList.value.findIndex((item: CartsState) => item.id === cart.id);
console.log(selListIndex.value)
} else {
isSelGift.value = false
selListIndex.value = list.value.findIndex((item: CartsState) => item.id === cart.id);
}
}
const basic_msg = {
number: 1,
is_gift: 0,
is_temporary: 0,
discount_sale_amount: 0,
discount_sale_note: "",
is_print: 1,
pro_group_info: '',
is_wait_call: 0,
product_name: "",
remark: "",
sku_id: '',
product_type: ''
};
function cartsPush(data: any) {
sendMessage('add', { ...basic_msg, ...data });
}
function add(data: any) {
const msg = { ...basic_msg, ...data };
const hasCart = list.value.find((cartItem) => {
return cartItem.product_id == msg.product_id && cartItem.sku_id == msg.sku_id;
});
if (hasCart) {
update({ ...hasCart, ...msg, number: hasCart.number * 1 + msg.number * 1 });
} else {
sendMessage('add', msg);
}
}
function changeTable(newVal: string | undefined) {
table_code.value = `${newVal}`;
$initParams.table_code = newVal;
concocatSocket();
}
function rotTable(newVal: string | number, cart_id = []) {
sendMessage('rottable', {
new_table_code: newVal,
table_code: table_code.value,
cart_id
});
}
function del(data: any) {
sendMessage('del', { id: data.id });
}
function update(data: any) {
const suitNum = data.skuData ? (data.skuData.suitNum || 1) : 1;
if (data.number * 1 < suitNum * 1) {
return sendMessage('del', data);
}
const pack_number = dinnerType.value === 'take-out' ? data.number : data.pack_number;
sendMessage('edit', { ...data, pack_number });
}
function updateTag(key: string, val: any, cart: CartsState = selCart.value) {
const skuData = cart.skuData || { suitNum: 1 };
if (cart.number * 1 < skuData.suitNum * 1) {
return sendMessage('del', cart);
}
if (key === 'discount_sale_amount' && val * 1 <= 0) {
return ElMessage.error('价格不能为0');
}
const msg = { ...cart, [key]: val };
if (key === 'number' && dinnerType.value === 'take-out') {
msg.pack_number = val;
}
sendMessage('edit', msg);
}
function changePack(is_pack: number | string) {
if (!isEmpty.value) {
sendMessage('batch', { is_pack });
}
}
function clear() {
sendMessage('cleanup', {});
}
function dataReset() {
selListIndex.value = -1;
selPlaceNum.value = -1;
isOldOrder.value = false;
isSelGift.value = false;
list.value = [];
giftList.value = [];
oldOrder.value = { detailMap: [], originAmount: 0 };
vipUser.value = {};
cartOrder.value = {};
}
function nowCartsClear() {
selListIndex.value = -1;
isOldOrder.value = false;
list.value = [];
giftList.value = [];
cartOrder.value = {};
userPoints.value = 0;
}
interface GroupSnap {
goods: { [key: string]: any }[];
}
function findInGroupSnapSku(groupSnap: GroupSnap[], sku_id: string | number) {
for (let i in groupSnap) {
const sku = groupSnap[i].goods.find(v => v.sku_id == sku_id);
if (sku) return sku;
}
}
async function getOldOrder(table_code: string | number) {
const res = await orderApi.getHistoryList({ tableCode: table_code });
if (res) setOldOrder(res);
}
function getProductDetails(v: { product_id: string, sku_id: string }) {
const goods = goodsMap[v.product_id];
console.log('getProductDetails', goods)
if (!goods) return undefined;
let skuData = goods?.skuList.find((sku: { id: string, salePrice: number }) => sku.id == v.sku_id);
if (!skuData && goods.type == 'package') {
const SnapSku = findInGroupSnapSku(goods.groupSnap, v.sku_id);
skuData = { ...SnapSku, salePrice: SnapSku ? SnapSku.price : 0 };
}
if (skuData) {
return {
salePrice: skuData.salePrice || 0,
memberPrice: skuData.memberPrice || 0,
coverImg: goods.coverImg,
name: goods.name,
specInfo: skuData.specInfo,
packFee: goods.packFee || 0,
type: goods.type,
skuData
};
}
return undefined;
}
function returnDetailMap(data: any) {
const newData: { [key: string]: any } = {};
for (let i in data) {
newData[i] = data[i].map((v: any) => {
const skuData = getProductDetails({ product_id: v.productId, sku_id: v.skuId });
return {
...v,
...skuData,
placeNum: v.placeNum,
number: v.num,
id: v.id,
pack_number: v.packNumber,
discount_sale_amount: v.discountSaleAmount * 1 || 0,
is_print: v.isPrint,
is_wait_call: v.isWaitCall,
is_gift: v.isGift,
is_temporary: v.isTemporary,
discount_sale_note: v.discountSaleNote,
product_name: v.productName,
sku_name: v.skuName,
sku_id: v.skuId,
product_type: v.productType
};
});
}
return newData;
}
function setOldOrder(data: any) {
oldOrder.value = {
...data,
detailMap: returnDetailMap(data.detailMap)
};
}
let $initParams = {} as ApifoxModel;
async function init(initParams: ApifoxModel, $oldOrder: any | undefined) {
await getGoods({});
if ($oldOrder) setOldOrder($oldOrder);
else oldOrder.value = { detailMap: [] };
if (initParams) {
initParams.table_code = initParams.table_code || '';
table_code.value = initParams.table_code;
$initParams = initParams;
}
concocatSocket($initParams);
}
function concocatSocket(initParams = $initParams) {
WebSocketManager.subscribeToTopic(initParams, (msg) => {
console.log("收到消息:", msg);
if (msg.hasOwnProperty('status') && msg.status !== 1) {
return ElMessage.error(msg.message || '操作失败');
}
if (msg?.data) {
if (Array.isArray(msg.data) && msg.data.length && msg.data[0].table_code) {
table_code.value = msg.data[0].table_code;
} else if (msg.data.table_code) {
table_code.value = table_code.value || msg.data.table_code;
} else if (msg.table_code) {
table_code.value = table_code.value || msg.table_code;
}
}
if (msg.operate_type === "manage_init") {
isLinkFinshed.value = true;
list.value = msg.data.filter((v: Record<string, any>) => {
if (v.is_temporary) return v;
const skuData = getProductDetails({ product_id: v.product_id, sku_id: v.sku_id });
if (skuData) {
Object.keys(skuData).forEach(key => {
v[key] = skuData[key];
});
} else {
del({ id: v.id });
return false;
}
return !v.is_gift;
});
giftList.value = msg.data.filter((v: Record<string, any>) => {
if (v.is_temporary) return v && v.is_gift;
const skuData = getProductDetails({ product_id: v.product_id, sku_id: v.sku_id });
if (skuData) {
Object.keys(skuData).forEach(key => {
v[key] = skuData[key];
});
} else {
del({ id: v.id });
return false;
}
return v.is_gift;
});
cartOrder.value = msg.data.reduce((obj: Record<string, number>, item: any) => {
obj[item.id] = new Date(item.create_time).getTime();
return obj;
}, {});
}
if (msg.operate_type === "manage_add") {
if (list.value.find(v => v.id == msg.data.id)) {
return ElMessage.warning(msg.message || '该商品已存在');
}
const skuData = getProductDetails({ product_id: msg.data.product_id, sku_id: msg.data.sku_id });
console.log('skuData', skuData);
const newGoods = { ...skuData, ...msg.data };
console.log('newGoods', newGoods)
list.value.push(newGoods);
return ElMessage.success(msg.message || '添加成功');
}
if (msg.operate_type === "manage_edit") {
const newCart = msg.data;
const listIndex = list.value.findIndex(item => item.id === newCart.id);
const giftIndex = giftList.value.findIndex(item => item.id === newCart.id);
if (giftIndex > -1) {
if (!newCart.is_gift) {
giftList.value.splice(giftIndex, 1);
list.value.push({ ...giftList.value[giftIndex], ...newCart });
cartOrder.value[newCart.id] = new Date().getTime();
} else {
giftList.value[giftIndex] = { ...giftList.value[giftIndex], ...newCart };
}
}
if (listIndex > -1) {
if (newCart.is_gift) {
list.value.splice(listIndex, 1);
giftList.value.push({ ...list.value[listIndex], ...newCart });
delete cartOrder.value[newCart.id];
} else {
list.value[listIndex] = { ...list.value[listIndex], ...newCart };
}
}
ElMessage.success(msg.message || '修改成功');
}
if (msg.operate_type === "manage_del") {
const cartId = Array.isArray(msg.data) ? msg.data[0].id : msg.data.id;
const listIndex = list.value.findIndex(item => item.id == cartId);
const giftIndex = giftList.value.findIndex(item => item.id == cartId);
if (listIndex > -1) list.value.splice(listIndex, 1);
if (giftIndex > -1) giftList.value.splice(giftIndex, 1);
delete cartOrder.value[cartId];
return ElMessage.success(msg.message || '删除成功');
}
if (msg.operate_type === "manage_cleanup") {
nowCartsClear();
getOldOrder(msg.data.table_code);
}
if (msg.operate_type === "batch") {
concocatSocket({ ...$initParams, table_code: table_code.value });
}
if (msg.operate_type === "product_update") {
init($initParams, oldOrder.value);
}
if (msg.type === "bc") {
msg.operate_type = 'manage_' + msg.operate_type;
concocatSocket(initParams);
}
});
}
function disconnect() {
sendMessage('disconnect', undefined);
}
const delArr = ['skuData', 'coverImg', 'specInfo', 'placeNum', 'update_time', 'create_time', 'packFee', 'memberPrice', 'type'];
function sendMessage(operate_type: msgType, message: any) {
const msg = { ...message, operate_type, table_code: table_code.value };
for (let i in delArr) {
delete msg[delArr[i]];
}
WebSocketManager.sendMessage(msg);
}
function payParamsInit() {
coupons.value = []
clearMerchantReduction()
userPoints.value = 0;
}
return {
disconnect,
dinnerType,
changePack,
giftMoney,
goodsTotal,
isLinkFinshed,
setOldOrder,
singleDiscount,
vipDiscount,
dataReset,
useVipPrice,
changeUser,
packNum,
packFee,
isOldOrder,
oldOrder,
isCanSelectGroup,
goods,
selGoods,
cartsPush,
table_code,
updateTag,
list,
add,
del,
update,
init,
changeNumber,
isEmpty,
selCart,
totalNumber,
changeSelCart,
payMoney,
clear,
yiyouhui,
giftList,
changeTable,
rotTable,
getGoods,
setGoodsMap,
orderCostSummary,
// 暴露商家减免配置(供外部读取当前状态)
merchantReductionConfig,
// 暴露修改方法(供外部设置两种减免形式)
setMerchantFixedReduction,
setMerchantDiscountReduction,
clearMerchantReduction,
seatFeeConfig,
pointDeductionRule,
//使用积分数量
userPoints,
coupons,
setCoupons,
payParamsInit
};
});
export function useCartsStoreHook() {
return useCartsStore(store);
}