代客下单重构

This commit is contained in:
2025-04-29 10:33:00 +08:00
parent 1f59082bcb
commit 13ad5a0de4
15 changed files with 5345 additions and 1525 deletions

View File

@@ -1,7 +1,4 @@
<script setup>
import {
useCartsStore
} from '@/stores/carts.js';
import {
useNavbarStore
} from '@/stores/navbarStore';
@@ -35,9 +32,6 @@
await nextTick()
const store = useNavbarStore();
await store.initNavbarHeight();
getApp().globalData.websocket=null;
const cartsStore=useCartsStore()
cartsStore.isloading=false;
});
onShow(async () => {
try {

View File

@@ -1,360 +0,0 @@
import {
ref,
onMounted,
onBeforeUnmount
} from 'vue';
const useWebSocket = (options = {}) => {
const {
heartbeatInterval = 10000, //心跳是10秒一次
reconnectInterval = 3000, //重新连接间隔时间的一个参数
maxReconnectAttempts = 3, //最大重连接次数
initialReconnectInterval = 3000, // 初始重连间隔
initMessage,
initMessageRetryCount = 3, // 新增:初始化消息发送重试次数
initMessageRetryInterval = 2000, // 新增:初始化消息重试间隔
maxReconnectDuration = Infinity,
onMessage
} = options;
const autoReconnect = ref(true); //是否自动重新连接
const socketTask = ref(null);
const isConnected = ref(false); //表示是否已连接上。
const heartbeatTimer = ref(null); //心跳定时器
const reconnectTimer = ref(null); //重连定时器
const reconnectAttempts = ref(0); //重连的尝试次数
const isNetworkConnected = ref(true); //监听当前网络连接状态
const isManuallyClosed = ref(false); //是否是被手动关闭的
const receivedMessages = ref(); //储从 WebSocket 服务器接收到的消息
const initMessageSendAttempts = ref(0); //初始化连接多少次
const reconnectStartTime = ref(0); //新增:记录重连开始时间
const isPongReceived = ref(false)
const allowReconnect = ref(true); // 新增:控制是否允许重连
// 关闭现有连接并清理资源
const closeExistingConnection = () => {
if (socketTask.value) {
// 关闭 WebSocket 连接
socketTask.value.close({
success: () => {
console.log('WebSocket 连接已关闭');
},
fail: (err) => {
console.error('关闭 WebSocket 连接失败:', err);
}
});
// 清除心跳定时器
clearInterval(heartbeatTimer.value);
heartbeatTimer.value = null;
// 清除重连定时器
clearTimeout(reconnectTimer.value);
reconnectTimer.value = null;
// 标记连接已断开
isConnected.value = false;
}
};
const websocketsendMessage = (data) => {
uni.$u.debounce(sendMessage(data), 500)
}
// 连接 WebSocket
const connect = (connectMsg) => {
if (!isNetworkConnected.value) {
uni.showToast({
title: '网络未连接...',
icon: 'none'
})
setTimeout(() => {
uni.pro.switchTab('index/index')
}, 1000)
console.log('网络未连接,暂不尝试连接 WebSocket');
return;
}
// 关闭现有连接并清理资源
closeExistingConnection();
socketTask.value = uni.connectSocket({
url: uni.conf.baseUrlwws,
success: (res) => {
isConnected.value = true;
// 监听初始化成功在开启心跳
startHeartbeat();
},
fail: () => {
console.error('WebSocket 连接失败,尝试重连');
if (autoReconnect.value && allowReconnect.value) {
handleReconnect();
}
}
});
if (socketTask.value) {
socketTask.value.onOpen(() => {
// 初始化 初始购物车
sendMessage(connectMsg ? connectMsg : initMessage)
});
socketTask.value.onMessage((res) => {
receivedMessages.value = JSON.parse(res.data)
websocketsendMessage({
type: 'receipt',
msg_id: receivedMessages.value.msg_id
})
if (onMessage) {
onMessage(receivedMessages.value)
}
// receivedMessages.value.push(list);
if (receivedMessages.value == 'ok' || receivedMessages.value.operate_type == 'init') {
console.log('初始化正常,心跳响应正常');
// 清除重连定时器
clearTimeout(reconnectTimer.value);
allowReconnect.value = false
reconnectTimer.value = null;
}
});
socketTask.value.onClose((res) => {
console.log(res, 'WebSocket 连接已关闭,尝试重连');
isConnected.value = false;
clearInterval(heartbeatTimer.value); // 停止心跳定时器
clearTimeout(reconnectTimer.value); // 清除重连定时器
if (res.code == '1006' && !allowReconnect.value) {
uni.showToast({
title: '网络异常,请重新扫码',
icon: 'none'
});
autoReconnect.value = false;
setTimeout(() => {
uni.pro.switchTab('index/index');
}, 1000)
return false;
}
if (autoReconnect.value && !isManuallyClosed.value) {
handleReconnect();
}
});
socketTask.value.onError((err) => {
console.error('WebSocket 连接发生错误:', err);
isConnected.value = false;
clearInterval(heartbeatTimer.value);
if (autoReconnect.value && !isManuallyClosed.value) {
handleReconnect();
}
});
} else {
console.error('socketTask 未正确初始化');
}
};
// 启动心跳机制
const startHeartbeat = () => {
if (!isNetworkConnected.value) {
console.log('网络未连接,暂停心跳');
uni.showToast({
title: '网络未连接...',
icon: 'none'
});
setTimeout(() => {
uni.pro.switchTab('index/index');
}, 1000);
return;
}
heartbeatTimer.value = setInterval(() => {
if (isConnected.value) {
console.log('发送心跳消息');
isPongReceived.value = false; // 每次发送心跳消息前重置标记
socketTask.value.send({
data: JSON.stringify({
type: 'ping_interval',
set: 'shopping'
}),
success: () => {
console.log('心跳消息发送成功');
const pongTimer = setTimeout(() => {
if (!isPongReceived.value) {
console.error('心跳超时,未收到响应,尝试重连');
clearInterval(heartbeatTimer.value);
if (autoReconnect.value && reconnectAttempts.value <
maxReconnectAttempts && allowReconnect.value) {
handleReconnect();
} else {
console.error('重连次数达到上限,停止重连和心跳');
clearInterval(heartbeatTimer.value);
autoReconnect.value = false;
uni.pro.switchTab('index/index');
}
}
}, heartbeatInterval * 1.2);
const handlePong = (res) => {
try {
let data = JSON.parse(res.data);
if (data.operate_type == "init" || (data.msg === 'ok' &&
data.msg_id ==
'ping_interval')) {
isPongReceived.value = true;
console.log('收到心跳响应,清除超时定时器');
clearTimeout(pongTimer);
}
} catch (error) {
console.error('解析心跳响应数据时出错:', error);
}
};
socketTask.value.onMessage(handlePong);
},
fail: () => {
console.error('心跳消息发送失败,尝试重连');
clearInterval(heartbeatTimer.value);
if (autoReconnect.value && reconnectAttempts.value <
maxReconnectAttempts && allowReconnect.value) {
handleReconnect();
} else {
console.error('重连次数达到上限,停止重连和心跳');
clearInterval(heartbeatTimer.value);
autoReconnect.value = false;
uni.pro.switchTab('index/index');
}
}
});
}
}, heartbeatInterval);
};
// 手动关闭连接
const closeSocket = () => {
isManuallyClosed.value = true;
closeExistingConnection();
};
// 发送消息
const sendMessage = async (data) => {
if (isConnected.value) {
await socketTask.value.send({
data: JSON.stringify(data),
success: () => {
// console.log('消息发送成功');
},
fail: () => {
// console.error('消息发送失败');
}
});
} else {
console.error('WebSocket 未连接,无法发送消息');
}
};
// 处理重连逻辑
const handleReconnect = () => {
if (!isNetworkConnected.value) {
console.log('网络未连接,暂停重连');
return;
}
if (isManuallyClosed.value) {
console.log('手动关闭连接,不进行重连');
return;
}
if (!allowReconnect.value) {
console.log('重连功能已关闭,不进行重连');
return;
}
if (reconnectAttempts.value < maxReconnectAttempts) {
reconnectAttempts.value++;
const reconnectInterval = initialReconnectInterval * Math.pow(2, reconnectAttempts.value - 1);
const randomizedInterval = reconnectInterval + Math.floor(Math.random() * 1000);
uni.showLoading({
title: `正在努力连接..`,
mask: true
})
console.log(`尝试第 ${reconnectAttempts.value} 次重连,重连间隔: ${randomizedInterval}ms...`);
reconnectTimer.value = setTimeout(() => {
connect();
}, randomizedInterval);
} else {
console.error('重连次数达到上限,停止重连');
uni.showToast({
title: '重连次数达到上限,停止重连',
icon: 'none'
});
clearInterval(heartbeatTimer.value);
autoReconnect.value = false;
setTimeout(() => {
uni.hideLoading();
uni.pro.switchTab('index/index');
}, 1000)
}
};
// / 网络状态监听
const initNetworkListener = () => {
uni.getSystemInfo({
success: (res) => {
if (res.platform !== 'devtools') {
uni.onNetworkStatusChange((statusRes) => {
isNetworkConnected.value = statusRes.isConnected;
if (statusRes.isConnected && !isManuallyClosed.value) {
console.log('网络已连接,尝试重新连接 WebSocket');
if (!isConnected.value && autoReconnect.value) {
connect();
}
} else if (!statusRes.isConnected) {
console.log('网络已断开,暂停 WebSocket 操作');
clearInterval(heartbeatTimer.value);
clearTimeout(reconnectTimer.value);
if (socketTask.value) {
socketTask.value.close();
isConnected.value = false;
}
}
});
}
},
fail: (err) => {
console.error('获取系统信息失败:', err);
}
});
uni.getNetworkType({
success: (res) => {
isNetworkConnected.value = res.networkType !== 'none';
if (!isNetworkConnected.value) {
console.log('初始网络未连接,暂不尝试连接 WebSocket');
}
}
});
};
// 页面显示,尝试连接 WebSocket
const onShowconnect = () => {
if (autoReconnect.value) {
uni.showLoading({
title: `尝试再次连接`,
mask: true
})
connect();
}
}
onBeforeUnmount(() => {
closeSocket();
});
return {
isConnected,
sendMessage,
closeSocket,
receivedMessages,
closeExistingConnection,
connect,
onShowconnect,
initNetworkListener,
connect
};
};
export default useWebSocket;

View File

@@ -0,0 +1,244 @@
import { error } from "../../uni_modules/uview-plus";
// import qs from "qs";
const qs = {
stringify: (obj) => {
const str= Object.keys(obj).reduce((prve, key) => {
return prve + `${key}=${obj[key]}&`
}, '?')
return str.substring(0,str.length-1)
}
}
function ElMessage() {
}
function ElMessageBox() {
}
const user = {
userInfo: {
shopId: ''
}
}
export interface ApifoxModel {
account : string;
operate_type : string;
shop_id : string;
table_code ?: string;
type : string;
[property : string] : string | number | undefined; // 限制额外属性类型
}
export type msgType = 'add' | 'reduce' | 'remove' | 'edit' | 'init' | 'cleanup' | 'del' | 'rottable' | 'batch' | 'disconnect' | 'clearOrder';
class WebSocketManager {
private client : WebSocket | null = null;
private connected : boolean = false;
private shop_id = user.userInfo.shopId ? String(user.userInfo.shopId) : '';
private autoConnect : boolean = true;
private initParams : ApifoxModel = {
type: 'shopping',
account: this.shop_id || '', // 提供默认值
operate_type: 'init',
table_code: '',
shop_id: this.shop_id || '', // 提供默认值
};
private onMessage : (message : any) => void = () => { };
private messageHandlers : Map<string, ((message : string) => void)[]> = new Map();
private type : string = 'shopping';
private reconnectAttempts = 0;
private maxReconnectAttempts = Infinity; // 自定义最大重试次数
private reconnectDelay = 5000; // 重试延迟(单位:毫秒)
private timer : any | null = null;
private reconnectTimer : any | null = null
connectSocketSuccsss(){
if(!this.client){
return
}
this.client.onOpen(() => {
this.connected = true;
console.log("WebSocket 连接已建立");
console.log(this.initParams);
this.sendMessage(this.initParams);
this.clearTimer();
this.timer = setInterval(() => {
this.sendMessage({ type: "ping_interval", set: 'shopping' }, false);
}, 1000 * 10);
})
this.client.onClose(() => {
this.clearTimer();
this.connected = false;
console.log("WebSocket 连接已断开");
if (this.autoConnect) {
this.reconnect(); // 自动重连
}
})
this.client.onError((error : Event) => {
this.clearTimer();
console.error("WebSocket 发生错误:", error);
this.reconnect(); // 自动重连
})
this.client.onMessage((event : MessageEvent) => {
try {
const message = JSON.parse(event.data);
if (message && message.msg_id) {
this.onMessageHandler({ msg_id: message.msg_id });
}
this.onMessage(message);
} catch (e) {
console.error("消息解析失败:", e);
}
})
}
// 初始化 WebSocket 客户端
setupWebSocket() {
// #ifdef H5
const endpoint = "http://192.168.1.31:2348/"
// #endif
// #ifdef MP-WEIXIN
const endpoint = "ws://192.168.1.31:2348/"
// #endif
if (!endpoint) {
console.warn("WebSocket 已被禁用,请在配置文件中配置 VITE_APP_WS_ENDPOINT");
return;
}
if (this.client && this.connected) {
console.log("客户端已存在并且连接正常");
return this.client;
}
const url = qs.stringify(this.initParams);
// this.client = new WebSocket(endpoint + '?' + url);
this.client = uni.connectSocket({
url: endpoint + url,
success: (res) => {
console.log('连接成功');
},
fail: (err) => {
console.error(error(err))
console.error('WebSocket 连接失败,尝试重连');
}
});
this.connectSocketSuccsss()
console.log('this.client');
console.log(this.client);
}
// 消息回执
public onMessageHandler(data : any) {
if (this.client && this.connected) {
this.client.send({data:JSON.stringify({ ...data, type: 'receipt' })});
}
}
// 订阅主题
public subscribeToTopic(initParams : ApifoxModel, onMessage : (message : any) => void) {
console.log(`正在订阅主题: `, initParams);
this.initParams = { ...this.initParams, ...initParams };
this.disconnect();
this.setupWebSocket();
this.onMessage = onMessage;
this.autoConnect = true
}
public sendMessage(message : any, isSendInitParams : boolean = true) {
if (!this.client || !this.connected) {
// ElMessage.error('发送失败,已重新连接,请重新操作');
this.reconnect();
return;
}
if (message.operate_type == 'disconnect') {
this.disconnect();
return
}
const msg = JSON.stringify(isSendInitParams ? { ...this.initParams, ...message } : message);
try {
this.client?.send({data:msg});
} catch (error) {
console.error("发送消息失败:", error);
// ElMessage.error('发送失败,已重新连接,请重新操作');
this.reconnect();
}
}
public canSendMessage() {
return this.client && this.connected;
}
// 断开 WebSocket 连接
public disconnect() {
if (this.client && this.connected) {
console.log("断开 WebSocket 连接");
this.clearTimer();
try {
if(this.client){
this.client.closeSocket();
}
} catch (error) {
//TODO handle the exception
console.error('关闭WebSocket连接失败')
}
this.client = null;
this.connected = false;
this.autoConnect = false;
}
}
// 自动重连机制
private reconnect() {
if (!this.autoConnect) {
return;
}
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`尝试第 ${this.reconnectAttempts} 次重连...`);
this.reconnectTimer = setTimeout(() => {
this.setupWebSocket();
}, this.reconnectDelay);
} else {
clearTimeout(this.reconnectTimer);
console.error("达到最大重连次数,停止重连");
ElMessageBox.confirm('达到最大重连次数' + this.maxReconnectAttempts + '次,已停止重连,是否立即重连?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
callback: (action : string) => {
console.log(action);
if (action == 'confirm') {
this.setupWebSocket();
this.reconnectAttempts = 0;
}
}
});
}
}
// 清除定时器
private clearTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
if (this.reconnectTimer) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null;
}
}
}
export default new WebSocketManager();

View File

@@ -260,10 +260,6 @@
</template>
<script setup>
import {
useCartsStore
} from '@/stores/carts.js';
const cartStore = useCartsStore()
import orderItemVue from './order-item.vue';
import {
ref,

View File

@@ -140,8 +140,8 @@
} from '@/common/api/shop/index.js'
import {
useCartsStore
} from '@/stores/carts.js';
import useWebSocket from '@/common/js/carts-websocket.js';
} from '@/stores/carts';
import useWebSocket from '@/common/js/carts-websocket';
const cartStore = useCartsStore()
let cartsSocket = null
watch(() => cartStore.goodsIsloading, (newValue) => {

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,6 @@
<up-popup :show="showCart" :round="20" :safeAreaInsetBottom="false" :zIndex="98" :overlayStyle="{ zIndex: 98 }"
@close="close">
<view class="cart-list-wrap">
<!-- <view class="cart-header flex-between">
<view class="num">已点 {{ cartLists_count }} </view>
<view class="clear" @click="cartclear">
<up-icon name="trash" color="#999"></up-icon>
<text class="t">清空</text>
</view>
</view> -->
<scroll-view scroll-y class="scroll-view">
<view class="list-wrap">
<view v-if="cartList.length>0">
@@ -29,15 +22,15 @@
<up-image :src="item.coverImg" width="80" radius="10" height="80"></up-image>
</view>
<view class="info">
<view class="name"> {{item.cartListinfo.is_temporary == 1?'临时菜' :item.name }}
<view class="name"> {{item.is_temporary == 1?'临时菜' :item.name }}
</view>
<view class="select-sku-wrap" v-if="item.type == 'sku'">
<text v-for="i in item.skuList" :key="i.id">
{{item.cartListinfo.sku_id == i.id? i.name:"" }}
<text >
{{item.skuData.name||''}}
</text>
</view>
<view class="select-sku-wrap" v-if="item.type == 'package'">
<view v-for="(a,b) in dataprocessing(item.cartListinfo)" :key="b">
<view v-for="(a,b) in dataprocessing(item)" :key="b">
<!-- <view>{{a.title}}</view> -->
<text v-for="i in a.goods" :key="i.proId" style="margin-left: 4rpx;">
{{i.proName }}
@@ -47,15 +40,14 @@
<view class="price-wrap" style="padding-top: 0;">
<view class="price">
<text class="i"></text>
<!-- 会员价与价格 -->
<text class="price" v-if="item.type == 'sku'">
<text v-for="i in item.skuList" :key="i.id">
<!-- -->
{{item.cartListinfo.sku_id == i.id?(shopInfo.isVip ==1 && shopInfo.isMemberPrice==1?(i.memberPrice || i.salePrice):i.salePrice):''}}
<!-- 会员价 -->
<text class="price" v-if="shopInfo.isVip ==1 && shopInfo.isMemberPrice==1">
<text >
{{item.memberPrice || item.salePrice}}
</text>
</text>
<text class="price" v-else>
{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1?(item.memberPrice || item.salePrice):item.salePrice}}
{{item.salePrice}}
</text>
<!-- <text class="originalprice"
v-if="item.originPrice">¥{{item.originPrice}}</text>
@@ -66,16 +58,16 @@
<view class="operation-wrap">
<view class="btn">
<up-icon color="#E8AD7B" name="minus-circle" size="25"></up-icon>
<view class="btnClick" @click="cartListadd(item,'-')"></view>
<view class="btnClick" @click="changeNumber(-1,item)"></view>
</view>
<text class="num">{{ ifcartNumber(item) }}</text>
<text class="num">{{ item.number*1 }}</text>
<view class="btn" v-if="item.type !='package'">
<!-- <up-icon name="plus-circle-fill"
:color="{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1? '#CECECE' : '#E9AB7A'"
size="25"></up-icon> -->
<up-icon name="plus-circle-fill" color="#E8AD7B"
size="25"></up-icon>
<view class="btnClick" @click="cartListadd(item,'+')"></view>
<view class="btnClick" @click="changeNumber(1,item)"></view>
</view>
</view>
</view>
@@ -104,7 +96,7 @@
} from '@/stores/user.js';
// 定义自定义事件
const emits = defineEmits(['customevent', 'close', 'clickcancelOrder']);
const emits = defineEmits(['changeNumber', 'close', 'clearCarts']);
const props = defineProps({
cartList: {
@@ -127,54 +119,16 @@
});
const shopInfo = uni.cache.get('shopInfo')
// 定义 ifcartNumber 计算属性方法
const ifcartNumber = computed(() => {
return (item) => {
// 如果 item 为空或者 cartNumber 不是字符串类型,返回 0
if (!item || typeof item.cartNumber !== 'string') {
return 0;
}
let numValue = parseFloat(item.cartNumber);
if (isNaN(numValue)) {
// 如果转换结果是 NaN说明 cartNumber 不是有效的数字字符串,返回 0
return 0;
}
// type string 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券
if (item.type === 'weight') {
// 如果类型是称重重量,将值保留两位小数
return parseFloat(numValue.toFixed(2));
} else {
// 如果类型是整数,将值转换为整数
return Math.round(numValue);
}
// 如果类型不匹配,返回原始值
return item.cartNumber;
};
})
const close = () => {
emits("close", false)
}
// 购物车加减
const cartListadd = async (item, i) => {
// 是否起售 如果小于或者大于都是1
const cartNumberFloat = parseFloat(item.cartNumber);
const suitNum = item.suitNum >= cartNumberFloat && i == '-' ? item.cartNumber : 1;
function changeNumber(step,item){
emits("changeNumber", step, item);
emits('customevent', {
id: item.cartListId ? item.cartListId : '',
type: 'shopping',
table_code: uni.cache.get('tableCode'),
shop_id: uni.cache.get('shopId'),
operate_type: calculateValue(item.cartNumber, i, suitNum) == 'del' ? 'del' : item.cartListId &&
item.cartNumber > 0 ? 'edit' : 'add',
product_id: item.id,
sku_id: item.skuId,
number: await calculateValue(item.cartNumber, i, suitNum),
is_print: 1,
suitNum: item.suitNum,
})
}
const dataprocessing = computed(() => {
return (item) => {
@@ -188,18 +142,6 @@
};
})
const clickcancelOrder = (i, key) => {
emits('clickcancelOrder', {
i,
key
})
emits('customevent', {
type: 'shopping',
table_code: uni.cache.get('tableCode'),
shop_id: uni.cache.get('shopId'),
operate_type: 'clearOrder',
})
}
const calculateValue = (cartNumber, i, step = 1) => {
if (i == '+') {
@@ -212,30 +154,10 @@
}
}
// 菜品备注修改
const productBlur = (item) => {
let params = {
"skuId": item.skuId,
"num": item.number, //数量
"type": item.type,
"isVip": item.isVip,
"productId": item.productId, //商品id
"note": item.note,
"shopId": this.shopId,
"userId": uni.cache.get('userInfo').id,
"tableId": this.tableCode,
}
this.$emit("addCart", params)
}
// 清空购物车
const cartclear = () => {
emits('customevent', {
type: 'shopping',
table_code: uni.cache.get('tableCode'),
shop_id: uni.cache.get('shopId'),
operate_type: 'cleanup',
})
emits('clearCarts')
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
</view>
</view>
<!-- 本店招牌菜 -->
<view class="panelfour" v-if="shopProductList.hots && shopProductList.hots.length > 0">
<view class="panelfour" v-if="carts.hotgoods && carts.hotgoods.length > 0">
本店招牌菜
</view>
<view class="panelfive ">
@@ -41,7 +41,7 @@
<scroll-view :scroll-x="true" :scroll-with-animation="false">
<view class="panelfive_list">
<view class="panelfiveitem" @click="clickspecifications(item,index,index,'热销')"
v-for="(item,index) in shopProductList.hots" :key="index">
v-for="(item,index) in carts.hotgoods" :key="index">
<image class="panelfiveitemimage" :src="item.coverImg" mode="aspectFill"></image>
<view class="vifgoodsImg flex-center"
v-if="item.isSale == 0 || (item.isSaleTime == 0 && !item.isSaleTimeshow) || item.isSoldStock == 1 || (item.isStock == 1 && item.stockNumber <= 0)">
@@ -84,7 +84,7 @@
:class="shopInfo.isVip == 0 || shopInfo.isMemberPrice==0?'lineThrough':''">¥</text>
<!-- 会员价与价格 -->
<text class="price">
{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1?(item.memberPrice||item.salePrice):item.salePrice}}
{{carts.useVipPrice?(item.memberPrice||item.salePrice):item.salePrice}}
</text>
<!-- 单位 -->
<text class="unit" v-if="item.unitName">/{{item.unitName}}</text>
@@ -106,7 +106,7 @@
:class="shopInfo.isVip ==0 || shopInfo.isMemberPrice==0?'lineThrough':''">¥</text>
<!-- 会员价与价格 -->
<text class="price">
{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1?(item.memberPrice|| item.salePrice):item.salePrice}}
{{carts.useVipPrice?(item.memberPrice|| item.salePrice):item.salePrice}}
</text>
<text class="unit" v-if="item.unitName">/{{item.unitName}}</text>
<!-- <text v-if="item.suitNum>1 && item.type!= 'sku'"
@@ -153,14 +153,14 @@
<view class="left" :style="{top: `${store.height}px`}">
<scroll-view :scroll-into-view="leftIntoView" :scroll-with-animation="false" :scroll-y="true"
:style="{height:`${store.screenHeight - store.height}px`}" style="padding-bottom: 200rpx;">
<view class="item" v-for="(item,index) in shopProductList.productInfo" :key="index"
<view class="item" v-for="(item,index) in carts.groupGoods" :key="index"
:class="{ 'active':index==leftIndex }" :id="'left-'+index" :data-index="index"
@click="leftTap(index)"><text>{{item.name}}</text></view>
</scroll-view>
</view>
<view class="main">
<view>
<view class="item main-item" v-for="(item,index) in shopProductList.productInfo" :key="index"
<view class="item main-item" v-for="(item,index) in carts.groupGoods" :key="index"
:id="'item-'+index">
<view class="title">
<view>{{item.name}}</view>
@@ -175,19 +175,25 @@
src="https://czg-qr-order.oss-cn-beijing.aliyuncs.com/index/1.gif" mode="" v-else
:lazy-load="true">
</image>
<view class="vifgoodsImg"
v-if="item1.isSale == 0 || (item1.isSaleTime == 0 && !item1.isSaleTimeshow) || item1.isSoldStock == 1 || (item1.isStock == 1 && item1.stockNumber <= 0)">
<image v-if="item1.isSale == 0" src="@/static/ztt/icon_goods_yxj.svg"
style="width:200rpx; height: 100%;" mode=""></image>
<image v-else-if="(item1.isSaleTime == 0 && !item1.isSaleTimeshow)"
src="@/static/ztt/icon_goods_wks.svg" style="width:200rpx; height: 100%;"
mode=""></image>
<image v-else-if="item1.isSoldStock == 1" src="@/static/ztt/icon_goods_sq.svg"
style="width:200rpx; height: 100%;" mode=""></image>
<image v-else-if="item1.isStock == 1 && item1.stockNumber <= 0"
src="@/static/ztt/icon_goods_kcbz.svg" style="width:200rpx; height: 100%;"
mode=""></image>
</view>
<block>
<view class="vifgoodsImg" v-if="item1.isSale == 0">
<image src="@/static/ztt/icon_goods_yxj.svg" style="width:200rpx; height: 100%;"
mode=""></image>
</view>
<view class="vifgoodsImg"
v-else-if="item1.isSaleTime == 0 && !item1.isSaleTimeshow">
<image src="@/static/ztt/icon_goods_wks.svg" style="width:200rpx; height: 100%;"
mode=""></image>
</view>
<view class="vifgoodsImg" v-else-if="item1.isSoldStock == 1">
<image src="@/static/ztt/icon_goods_sq.svg" style="width:200rpx; height: 100%;"
mode=""></image>
</view>
<view class="vifgoodsImg" v-else-if="item1.isStock == 1 && item1.stockNumber <= 0">
<image src="@/static/ztt/icon_goods_kcbz.svg"
style="width:200rpx; height: 100%;" mode=""></image>
</view>
</block>
<view v-if="index=='0'" class="topSort" :class="'c'+(index1+1)">TOP{{index1+1}}</view>
<view class="goods_right" style="overflow: hidden;">
<view class="name">{{ item1.name }}</view>
@@ -205,8 +211,11 @@
<view class="money">
<view></view>
<text class="money_num" style="margin-right: 10rpx;">
{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1 ?(item1.memberPrice||item1.salePrice):item1.salePrice}}
{{carts.useVipPrice?(item1.memberPrice||item1.salePrice):item1.salePrice}}
</text>
<!-- <text class="money_num" style="margin-right: 10rpx;">
{{carts.useVipPrice ?(item1.memberPrice||item1.salePrice):item1.salePrice}}
</text> -->
<text v-if="item1.unitName">/{{item1.unitName}}</text>
</view>
<view class="flex-end">
@@ -221,7 +230,7 @@
<view class="money">
<view></view>
<text class="money_num">
{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1?(item1.memberPrice||item1.salePrice):item1.salePrice}}
{{carts.useVipPrice?(item1.memberPrice||item1.salePrice):item1.salePrice}}
</text>
<text class="money_num" v-if="item1.unitName">/{{item1.unitName}}</text>
<!-- <text v-if="item1.suitNum>1 && item1.type!= 'sku'"
@@ -267,21 +276,17 @@
</view>
<confirmorder ref="confirmorderref" :cartLists_count="cartLists_count" :cartList="matchedProducts"
:totalPrices='totalPrices' :confirmordershow="confirmordershow"
@close="confirmordershow = !confirmordershow" @customevent='websocketsendMessage' :orderinfo="orderinfo">
</confirmorder>
<!-- 店铺详情 -->
<shopindex ref="showShopInfoRef"></shopindex>
<!-- 购物车 -->
<shoppingCartes :cartLists_count="cartLists_count" :cartList="matchedProducts" :showCart="showCart"
@customevent='websocketsendMessage' @close="showCart = !showCart" :orderinfo="orderinfo"
@clickcancelOrder='clickcancelOrder' v-if="cartLists_count > 0">
<shoppingCartes :cartLists_count="carts.totalNumber" :cartList="carts.list" :showCart="showCart"
@clearCarts="clearCarts" @changeNumber="cartsChangeNumber" @close="showCart = !showCart"
:orderinfo="orderinfo" v-if="!carts.isEmpty">
</shoppingCartes>
<!-- 显示购物车栏 -->
<view class="cart-wrap" v-if="cartLists_count > 0 && !confirmordershow && isBusinessTime">
<view class="cart-wrap" v-if="!carts.isEmpty && isBusinessTime">
<view class="cart-content">
<view class="left">
<view class="iconBox">
@@ -289,11 +294,11 @@
src="https://czg-qr-order.oss-cn-beijing.aliyuncs.com/shopDetails/shopIcon.png"
mode="aspectFill" @click="Historicalorders(true)">
</image>
<text class="u-badge"> {{cartLists_count<99?cartLists_count:'99+'}} </text>
<text class="u-badge"> {{carts.totalNumber <99?carts.totalNumber:'99+'}} </text>
</view>
<text class="i"></text>
<text class="num">{{totalPrices}}</text>
<text class="num">{{carts.payMoney}}</text>
</view>
<view class="btn" @tap="$u.debounce(orderdetail, 500)">
<text class="t">结算</text>
@@ -366,7 +371,7 @@
<view class="price" v-if="specifications.item.type != 'package' && specifications.item.result">
<text class="i"></text>
<text
class="num">{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1? (specifications.item.result.memberPrice||specifications.item.result.salePrice):specifications.item.result.salePrice}}</text>
class="num">{{carts.useVipPrice? (specifications.item.result.memberPrice||specifications.item.result.salePrice):specifications.item.result.salePrice}}</text>
<text class="i" v-if="specifications.item.unitName">/{{specifications.item.unitName}}</text>
<text v-if="specifications.item.result.suitNum>1">
{{specifications.item.result.suitNum}}{{specifications.item.result.unitName}}起点
@@ -375,7 +380,7 @@
<view class="price" v-else>
<text class="i"></text>
<text class="num">
{{shopInfo.isVip ==1 && shopInfo.isMemberPrice==1? (specifications.item.memberPrice||specifications.item.salePrice):specifications.item.salePrice}}
{{carts.useVipPrice? (specifications.item.memberPrice||specifications.item.salePrice):specifications.item.salePrice}}
</text>
<text class="i" v-if="specifications.item.unitName">
/{{specifications.item.unitName}}
@@ -418,11 +423,36 @@
<image class="img" src="@/static/history.png" mode=""></image>
<text>已下单菜品</text>
</view>
<Loading :isLoading="isLoading" />
<Loading :isLoading="!carts.isLinkFinshed" />
</view>
</template>
<script setup>
import {productStore} from '@/stores/user'
import {
useCartsStore
} from '@/stores/carts'
//购物车
const carts = useCartsStore();
function cartsChangeNumber(step, item) {
carts.changeNumber(step * 1, item);
}
function clearCarts() {
carts.clear();
}
//用户信息
const userStore=productStore()
// 获取用户信息
userStore.actionsAPIuser(res=>{
console.log('userStore.actionsAPIuser');
console.log(res);
carts.changeUser(user)
})
import {
ref,
reactive,
@@ -445,7 +475,7 @@
import Nav from '@/components/CustomNavbar.vue';
import shopindex from './components/shopindex.vue'
import shoppingCartes from './components/shoppingCartes.vue'
import confirmorder from './components/confirmorder.vue'
// import confirmorder from './components/confirmorder.vue'
import Loading from '@/components/Loading.vue';
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween'
@@ -486,11 +516,6 @@
const storeMemberpay = Memberpay();
const store = useNavbarStore();
import {
productStore
} from '@/stores/user.js';
const userStore = productStore();
// 金额管理
import {
@@ -516,8 +541,7 @@
//店铺详情
const showShopInfoRef = ref(null)
// 初始加载中
const isLoading = ref(true);
//调用shop组件
const callChildMethod = () => {
@@ -541,7 +565,7 @@
// * 图片加载
const imageLoaded = (item, index, index1) => {
// shopProductList.productInfo[index].products[index1]['imgLoad'] = true;
// carts.groupGoods[index].products[index1]['imgLoad'] = true;
}
// 计算左侧位置
@@ -812,132 +836,6 @@
//添加购物车数量
const shopCartNumber = ref(0)
// 多规格 套餐添加数量
const shopCart = async (i) => {
if (i == '-' && shopCartNumber.value >= 0) {
shopCartNumber.value = 0
return false;
}
let res = await shoppingcart()
if (i == '-') {
if (!res && shopCartNumber.value == specifications.item.suitNum) {
uni.showToast({
title: `起点${specifications.item.suitNum}`,
icon: 'none'
})
return false;
} else {
shopCartNumber.value--;
}
} else {
if (!res && shopCartNumber.value < 1) {
console.log(res, specifications)
if (specifications.type == 'sku') {
shopCartNumber.value = parseFloat(specifications.item.result.suitNum);
} else {
shopCartNumber.value = parseFloat(specifications.item.suitNum);
}
} else {
shopCartNumber.value++;
}
}
}
// 套餐比较两个对象是否相等
function isObjectEqual(obj1, obj2) {
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return obj1 === obj2;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) {
if (!isArrayEqual(obj1[key], obj2[key])) {
return false;
}
} else if (!isObjectEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
// 比较两个数组是否相等(忽略顺序)
function isArrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
const usedIndices = new Array(arr2.length).fill(false);
for (const item1 of arr1) {
let found = false;
for (let i = 0; i < arr2.length; i++) {
if (!usedIndices[i] && isObjectEqual(item1, arr2[i])) {
usedIndices[i] = true;
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
// 根据购物车的数据匹配选中的商品查找是否有匹配的数组
const matchingProduct = async (data) => {
return matchedProducts.value.find((product, index) => {
if (specifications.type == 'package') {
// 套餐
let result = false;
try {
if (product.type == "package") {
let res = JSON.parse(product.cartListinfo.pro_group_info);
result = isArrayEqual(res, selectedGroupSnap.value);
}
} catch (error) {
//TODO handle the exception
}
// 直接返回布尔值
return result;
} else if (specifications.item.type == 'sku') {
// 多规格
return product.cartListinfo.sku_id == data.id && product.cartListinfo.product_id == data
.productId
} else {
// 其他
return product.skuId == data.skuId && product.id == data.id
}
});
}
// 判断多规格和套餐 商品是否在购物车有数据
const shoppingcart = async () => {
let res = null
if (specifications.item.type == "package") {
if (!allConditionsSatisfied.value) {
return false
}
// 是否是套餐package
selectedGroupSnap.value = specifications.item.groupSnap.map((setmenu, index) => {
return {
...setmenu,
goods: selectedOptions.value[index]
};
});
res = await matchingProduct(selectedGroupSnap.value)
} else {
if (!canSubmit.value) {
return false
}
res = await matchingProduct(specifications.item.result)
}
return res;
}
// 提交选择并执行下一步操作的方法
const submitSelection = async () => {
@@ -965,7 +863,7 @@
selectedGroupSnap.value = []
}
websocketsendMessage({
carts.add({
id: res ? res.cartListId : '',
type: 'shopping',
suitNum: specifications.productListitem.suitNum,
@@ -982,6 +880,24 @@
is_print: 1,
product_type: specifications.item.type
})
// websocketsendMessage({
// id: res ? res.cartListId : '',
// type: 'shopping',
// suitNum: specifications.productListitem.suitNum,
// table_code: uni.cache.get('tableCode'),
// shop_id: uni.cache.get('shopId'),
// operate_type: res ? 'edit' : 'add',
// product_id: specifications.product_id,
// sku_id: specifications.sku_id,
// number: res ? await calculateValue(res.cartNumber, '+', shopCartNumber.value) :
// shopCartNumber
// .value,
// pro_group_info: selectedGroupSnap.value,
// goods_type: specifications.item.type == "package" ? 'package' : '',
// is_print: 1,
// product_type: specifications.item.type
// })
// 清空套餐选中
selectedGroupSnap.value = []
showShopsku.value = false
@@ -1068,6 +984,18 @@
return isInRange;
}
function addCarts(item, isWeight = false) {
if (isWeight) {
carts.add({
...item
});
return;
}
carts.add({
...item
});
}
// 单规格
const singleclick = async (item, i) => {
if (!isProductAvailable(item.days, item.startTime, item.endTime)) {
@@ -1077,26 +1005,18 @@
return false;
}
// 判断购物车是否有该选中商品
let res = null
try {
res = matchedProducts.value.find((product, index) => {
return product.skuId == item.skuId && product.id == item.id
});
} catch (error) {
//TODO handle the exception
}
// 保存这次点击的
specifications.productListitem = item
// 是否起售 如果小于或者大于都是1
let suitNum = 1;
const cartNumberFloat = parseFloat(item.cartNumber);
if (!res && item.suitNum > cartNumberFloat) {
suitNum = item.suitNum;
} else if (item.suitNum >= cartNumberFloat && i === '-') {
suitNum = item.cartNumber;
}
console.log(item);
addCarts({
suitNum: item.suitNum || 1,
product_id: item.id,
sku_id: item.skuList[0].id,
number: item.skuList[0].suitNum || 1,
product_type: item.type,
});
return
websocketsendMessage({
id: res ? item.cartListId : '',
type: 'shopping',
@@ -1134,190 +1054,62 @@
shop_id: uni.cache.get('shopId')
}
}
const {
isConnected,
sendMessage,
closeSocket: manualClose,
receivedMessages,
closeExistingConnection,
onShowconnect,
initNetworkListener
} = useWebSocket(options);
try{
carts.init({
type: 'shopping',
operate_type: 'init',
table_code: uni.cache.get('tableCode'),
shop_id: uni.cache.get('shopId')
}, {});
}catch(err){
console.error('购物车数据初始化失败')
}
function sendMessage() {}
function closeSocket() {}
function receivedMessages() {}
function closeExistingConnection() {}
function onShowconnect() {}
function initNetworkListener() {}
// const {
// isConnected,
// sendMessage,
// closeSocket: manualClose,
// receivedMessages,
// closeExistingConnection,
// onShowconnect,
// initNetworkListener
// } = useWebSocket(options);
//购物车显示
const showCart = ref(false)
// 提交订单显示
const confirmordershow = ref(false)
// 更新商品数量的方法
const updateProductQuantities = () => {
// 先将所有商品的 cartNumber 初始化为 0
shopProductList.hots.forEach((i) => {
i.cartNumber = 0
})
// 遍历商品列表二维数组
shopProductList.productInfo.forEach((group) => {
group.productList.forEach((product) => {
product.cartNumber = 0
});
});
// 再去更新数组的值
cartListFilter.value.forEach((cartItem) => {
shopProductList.productInfo.forEach((group) => {
group.productList.forEach((product) => {
if (product.id == cartItem.product_id && product.skuId == cartItem
.sku_id) {
product.cartNumber = cartItem.number
product.cartListId = cartItem.id
}
});
});
});
// 遍历购物车数组
cartListFilter.value.forEach((cartItem) => {
// 遍历商品列表二维数组
shopProductList.hots.forEach((group) => {
// 商品 id 匹配
if (group.id == cartItem.product_id) {
// 更新商品的数量
group.cartListId = cartItem.id
group.cartNumber = cartItem.number
}
});
});
}
//websocket产值
const websocketsendMessage = (data) => {
uni.$u.debounce(sendMessage(data), 500)
}
// 用于记录已经处理过的消息的 msg_id
const processedMessageIds = new Set();
// 监听接收到的消息变化
watchEffect(async () => {
if (isDataLoaded.value && receivedMessages.value) {
const Message = receivedMessages.value
if (Message) {
console.log(Message.data);
// 心跳返回 过滤
if (Message.type == "ping_interval" || Message.msg_id == "ping_interval") {
isLoading.value = false;
return false
}
// 检查消息是否已经处理过
if (processedMessageIds.has(Message.msg_id)) {
return;
}
processedMessageIds.add(Message.msg_id);
// 初始化
if (Message.operate_type == "init") {
cartStore.carts = Message.data
uni.hideLoading();
isLoading.value = false;
}
// 清空购物车
if (Message.operate_type == 'cleanup') {
cartStore.carts = []
setTimeout(() => {
Historicalorders()
}, 400)
showCart.value = false
}
// 删除除购物车
if (Message.operate_type == 'del' && Message.status == 1) {
// 优化:使用可选链操作符避免报错
cartStore.carts = cartStore.carts.filter(item => item.id !== Message.data?.id);
// cartStore.carts = cartStore.carts.filter(item => item.id != Message.data.id);
}
// 添加或者减少购物后返回
if (Message.operate_type == 'add' || Message.operate_type == 'edit') {
[Message.data].forEach((objA) => {
const index = cartStore.carts.findIndex((objB) => objB.id == objA.id);
if (index !== -1) {
cartStore.carts[index] = objA;
} else {
cartStore.carts.push(objA);
}
});
}
// 历史订单
if (Message.operate_type == 'clearOrder') {
Historicalorders()
}
// 购物车数据更新从新请求
if (Message.type == 'product' && Message.data_type == 'product_update' && Message
.operate_type == 'product_update') {
isDataLoaded.value = false;
productqueryProduct()
}
// 提示
if (Message.status == 0 && Message.type != 'no_suit_num') {
uni.showToast({
title: Message.msg,
icon: "none"
})
}
if (Message.type == 'no_suit_num') {
// console.log(specifications)
uni.showModal({
title: '提示',
showCancel: false,
content: '此商品库存不足起售数量!',
success: async (data) => {
await websocketsendMessage({
id: Message.id,
type: 'shopping',
table_code: uni.cache.get('tableCode'),
shop_id: uni.cache.get('shopId'),
operate_type: 'del',
is_print: 1,
})
}
});
}
//除去p 每次返回都回执消息
await websocketsendMessage({
type: 'receipt',
msg_id: Message.msg_id
})
// 初始化商品数量
await updateProductQuantities()
}
}
})
// 更新购物车数据shopProductList.hots
// 更新购物车数据carts.hotgoods
const matchedProducts = computed(() => {
if (cartStore.carts.length > 0) {
if (carts.list.length > 0) {
let Specialstop = null
try {
Specialstop = [...[{
id: "",
name: "",
productList: shopProductList.hots
}], ...shopProductList.productInfo]
productList: carts.hotgoods
}], ...carts.groupGoods]
} catch (error) {
Specialstop = shopProductList.productInfo
Specialstop = carts.groupGoods
//TODO handle the exception
}
return cartStore.carts.flatMap((cartItem) => {
return carts.list.flatMap((cartItem) => {
for (const group of Specialstop) {
for (const product of group.productList) {
if (product.id == cartItem.product_id) {
@@ -1351,26 +1143,10 @@
})
//删除某一个待支付订单
const clickcancelOrder = async (data) => {
if (data.i == 'all') {
await APIcancelOrder({
shopId: uni.cache.get('shopId'),
orderId: orderinfo.value.id,
})
} else {
await APIrmPlaceOrder({
shopId: uni.cache.get('shopId'),
orderId: orderinfo.value.id,
placeNum: data.key
})
}
Historicalorders()
}
// 储存是否存在多次下单
const orderinfo = ref({})
const confirmorderref = ref(null)
// const confirmorderref = ref(null)
// 结账
const orderdetail = async () => {
@@ -1379,17 +1155,12 @@
.cache.get(
'shopId')
})
return
try {
await Historicalorders()
} catch (error) {}
confirmordershow.value = true
showCart.value = false
}
// 历史订单
const Historicalorders = async (W) => {
console.log('Historicalorders');
console.log(W);
if (W) {
showCart.value = !showCart.value
} else {
@@ -1404,89 +1175,6 @@
}
}
// 提取合并 orderinfo.detailMap 数组的逻辑
function combineOrderInfoDetailMap(orderinfo) {
if (!orderinfo) return [];
let combinedArray = [];
for (const key in orderinfo.detailMap) {
if (orderinfo.detailMap.hasOwnProperty(key)) {
let subArray = orderinfo.detailMap[key];
combinedArray = [...combinedArray, ...subArray];
}
}
return combinedArray;
}
// 计算购物车商品费用
const totalPrices = computed(() => {
// 待支付订单
let cartone = 0
if (orderinfo.value) {
let combinedArray = [];
for (const key in orderinfo.value.detailMap) {
if (orderinfo.value.detailMap.hasOwnProperty(key)) {
let subArray = orderinfo.value.detailMap[key];
combinedArray = [...combinedArray, ...subArray]
}
}
// 购物车总数价格
cartone = combinedArray.reduce((total, item) => {
// 是否启用会员价 0否1是
if (shopInfo.isVip == 1 && shopInfo.isMemberPrice == 1) {
// memberPrice会员价
return total + (parseFloat(item.memberPrice || item.price) * parseFloat(item.num - item
.returnNum));
} else {
// salePrice销售价
return total + (parseFloat(item.price || 0) * parseFloat(item.num - item.returnNum));
}
}, 0);
}
// 购物车总数价格
let cart = 0
if (matchedProducts.value.length > 0) {
// 购物车总数价格
cart = matchedProducts.value.reduce((total, item) => {
if (item.type == 'sku') {
item.skuList.forEach((i, t) => {
if (item.cartListinfo.sku_id == i.id) {
item.memberPrice = i.memberPrice
item.salePrice = i.salePrice
}
})
}
// 是否启用会员价 0否1是
if (shopInfo.isVip == 1 && shopInfo.isMemberPrice == 1) {
// memberPrice会员价
return total + parseFloat(item.memberPrice || item.salePrice) * parseFloat(item
.cartNumber);
} else {
// salePrice销售价
return total + parseFloat(item.salePrice || 0) * parseFloat(item.cartNumber);
}
}, 0);
}
cart = parseFloat(cart)
// 向上取整并保留两位小数
return parseFloat(cart.toFixed(2));
});
// 计算购物车商品总数量
const cartLists_count = computed(() => {
const combinedOrderInfo = combineOrderInfoDetailMap(orderinfo.value);
const orderInfoCount = combinedOrderInfo.reduce((sum, item) => {
return sum + parseFloat(item.num);
}, 0);
const matchedProductsCount = matchedProducts.value.reduce((sum, item) => {
const num = typeof item.cartNumber === 'string' ? parseFloat(item.cartNumber) : item
.cartNumber;
return sum + num;
}, 0);
const totalCount = matchedProductsCount;
return parseFloat(totalCount.toFixed(2));
});
// 定义 ifcartNumber 计算属性方法 展示数量
const ifcartNumber = computed(() => {
@@ -1513,31 +1201,6 @@
};
})
// 计算处理后的购物车列表 // 用于筛选后的购物车数组
const cartListFilter = computed(() => {
// 使用 reduce 方法对 cartList 进行处理
const grouped = cartStore.carts.reduce((acc, item) => {
const productId = item.product_id;
const num = parseFloat(item.number);
if (!acc[productId]) {
// 如果 acc 中还没有该 product_id 的记录,创建一个新对象
acc[productId] = {
...item,
number: num
};
} else {
// 如果已经有该 product_id 的记录,将当前对象的 number 值累加到已有记录的 number 属性上
acc[productId].number += num;
}
return acc;
}, {});
// 将累加结果保留两位小数,并将对象转换为数组
return Object.values(grouped).map(item => ({
...item,
number: item.number.toFixed(2)
}));
})
const endTimeref = reactive({
startTime: '',
@@ -1568,80 +1231,25 @@
return currentTime >= startTime && currentTime <= endTime;
});
// 列表请求
const productqueryProduct = async () => {
cartStore.goodsIsloading = false;
try {
shopProductList.hots = await productminiApphotsquery()
shopProductList.productInfo = await APIgroupquery()
} catch (error) {
uni.showToast({
title: '网络不稳定,请重新扫码进入',
icon: 'none'
})
setTimeout(() => {
uni.pro.switchTab('index/index')
}, 1000)
}
if (shopProductList.productInfo.length > 0 || shopProductList.hots.length > 0) {
//TODO handle the exception
//第一步:将所有商品的 cartNumber 初始化为 0
shopProductList.productInfo.forEach((group) => {
group.productList.forEach(async (product) => {
product.cartNumber = 0;
product.isSaleTimeshow = await isProductAvailable(product.days, product
.startTime, product.endTime)
cartStore.setGoodsMap(product.id, product)
});
});
shopProductList.hots.forEach(async (i) => {
i.cartNumber = 0
i.isSaleTimeshow = await isProductAvailable(i.days, i.startTime, i.endTime)
cartStore.setGoodsMap(i.id, i)
})
cartStore.goodsIsloading = true;
scrollTopSize.value = 0
topArr.value = []
// userStore.actionsAPIuser()
// 数据可以更新
isDataLoaded.value = true;
// 历史订单
Historicalorders()
} else {
uni.showToast({
title: '暂无列表数据,请重新扫码',
icon: "none"
});
isDataLoaded.value = false;
setTimeout(() => {
uni.pro.switchTab('index/index')
}, 1000)
return false;
}
}
onLoad(async (e) => {
await proxy.$onLaunched;
})
onShow(async() => {
onShow(async () => {
// 监听页面显示和隐藏
onShowconnect()
let res = await APIhistoryOrder({
tableCode: uni.cache.get('tableCode'),
})
if(res){
if (res) {
orderinfo.value = {
id: res.id,
detailMap: res.detailMap,
placeNum: res.placeNum
}
}else{
} else {
orderinfo.value = {
id:''
id: ''
}
}
})
@@ -1662,14 +1270,11 @@
tableCode: uni.cache.get('tableCode'),
})
await productqueryProduct()
if (res && res.id && shopInfo.registerType == "after") {
toHistory()
return
}
setTimeout(() => {
// 启动网络监听
initNetworkListener()
getElementTop()
}, 500)
})

View File

@@ -1,65 +1,29 @@
<template>
<view class="u-p-30">
<view>
<up-button type="warning" @click="popupShow">初始化</up-button>
</view>
<view class="u-m-t-30">
<up-button type="primary" @click="toCreate">去下单</up-button>
</view>
<up-popup :show="show" mode="bottom" close-on-click-overlay @close="resetForm">
<view class="u-p-30">
<up-form label-width="80" ref="refForm">
<up-form-item label="台桌码">
<up-input v-model="form.tableCode" placeholder="请输入台桌码"></up-input>
</up-form-item>
</up-form>
<view class="u-flex gap-20 u-m-t-30">
<up-button @click="show=false">取消</up-button>
<up-button type="primary" @click="scanCodehandle">确定</up-button>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import {onLoad} from '@dcloudio/uni-app'
import { reactive,ref} from 'vue'
import {
productStore
} from '@/stores/user.js';
const store = productStore();
const scanCodehandle = async (i) => {
await store.scanCodeactions(form)
}
const show=ref(false);
const options=ref({})
function popupShow(){
show.value=true;
}
const refForm=ref(null);
const form=reactive({
tableCode:"40963902920"
ref
} from 'vue';
import {
useCartsStore
} from '@/stores/carts'
const carts = useCartsStore();
console.log(carts);
const table = ref({
tableCode: '40963902920',
})
function resetForm(){
form.tableCode=""
}
onLoad((opt)=>{
console.log(opt);
options.value=opt
})
function toCreate(){
uni.navigateTo({
url:'/pages/index/index'
})
}
carts.init({
table_code: table.value.tableCode,
shop_id:"29",
account:29
}, {});
</script>
<style lang="scss" scoped>
.gap-20{
.gap-20 {
gap: 20rpx;
}
</style>
</style>

View File

@@ -1,452 +0,0 @@
import {
defineStore
} from 'pinia';
import {
ref,
computed,
reactive,
watchEffect
} from 'vue';
import {
productminiApphotsquery,
APIgroupquery,
} from "@/common/api/product/product.js";
export const useCartsStore = defineStore('cart',
() => {
// const dinersNum = uni.cache.get('dinersNum')
// const isVip = uni.cache.get('orderVIP').isVip //此用户是否是会员
// const isMemberPrice = uni.cache.get('ordershopUserInfo').isMemberPrice //此店是否可以用会员
// const isTableFee = uni.cache.get('ordershopUserInfo').isTableFee //此店是否免桌位费
// const tableFee = uni.cache.get('ordershopUserInfo').tableFee //一个餐位费多钱
const goodsIsloading = ref(true);
//商品数据Map
const goodsMap = reactive({})
//获取商品数据
async function goodsInit() {
goodsIsloading.value=true;
//获取招牌菜商品
const hotres = await productminiApphotsquery();
for (let product of hotres) {
setGoodsMap(product.id, product)
}
//获取分组商品
const groupres = await APIgroupquery()
for (let group of groupres) {
for (let product of group.productList) {
setGoodsMap(product.id, product)
}
}
goodsIsloading.value = false
}
function setGoodsMap(product_id, data) {
goodsMap[product_id] = data;
}
//websocket回执
const websocketsendMessage = (data) => {
uni.$u.debounce(sendMessage(data), 500)
}
const isLoading = ref(true);
function getProductDetails(v) {
const goods = goodsMap[v.product_id]
if (!goods) {
return undefined
}
let skuData = undefined;
skuData = goods?.skuList.find((sku) => sku.id == v.sku_id);
// if (goods.type == 'package') {
// //套餐商品
// const SnapSku = findInGroupSnapSku(goods.groupSnap, v.sku_id)
// skuData = { ...SnapSku, salePrice: SnapSku ? SnapSku.price : 0 }
// } else {
// skuData = goods?.skuList.find((sku: { id: string, salePrice: number }) => sku.id == v.sku_id);
// }
skuData = goods?.skuList.find((sku) => sku.id == v.sku_id);
if (skuData) {
return {
...v,
productId: v.product_id,
salePrice: skuData ? skuData.salePrice : 0,
price: skuData ? skuData.salePrice : 0,
memberPrice: skuData ? skuData.memberPrice : 0,
coverImg: goods.coverImg,
productImg: goods.coverImg,
name: goods.name,
productName: goods.name,
specInfo: skuData.specInfo,
packFee: goods.packFee || 0,
type: goods.type,
skuData,
skuName: skuData.name,
num: v.number * 1
}
} else {
return undefined
}
}
// 用于记录已经处理过的消息的 msg_id
const processedMessageIds = new Set();
//购物车商品信息补全初始化
function cartsGoodsInfoInit(arr) {
carts.value = arr.map(v => {
return getProductDetails(v)
}).filter(v => v)
}
//收到socket消息时对购物车进行处理
async function onMessage(Message) {
console.log('Message');
console.log(Message);
if (Message) {
// 心跳返回 过滤
if (Message.type == "ping_interval" || Message.msg_id == "ping_interval") {
isLoading.value = false;
return false
}
// 检查消息是否已经处理过
if (processedMessageIds.has(Message.msg_id)) {
return;
}
processedMessageIds.add(Message.msg_id);
const msgData = Message.data
// 初始化
if (Message.operate_type == "init") {
console.log('carts init', msgData);
cartsGoodsInfoInit(msgData)
uni.hideLoading();
isLoading.value = false;
}
// 清空购物车
if (Message.operate_type == 'cleanup') {
carts.value = []
}
// 删除除购物车
if (Message.operate_type == 'del' && Message.status == 1) {
// 优化:使用可选链操作符避免报错
carts.value = carts.value.filter(item => item.id !== msgData?.id);
// carts.value = carts.value.filter(item => item.id != Message.data.id);
}
// 添加或者减少购物后返回
if (Message.operate_type == 'add' || Message.operate_type == 'edit') {
const index = carts.value.findIndex((v => v.id == msgData.id))
if (index !== -1) {
carts.value[index] = getProductDetails(msgData);
} else {
carts.value.push(getProductDetails(msgData));
}
}
// 历史订单
if (Message.operate_type == 'clearOrder') {}
// 购物车数据更新从新请求
if (Message.type == 'product' && Message.data_type == 'product_update' && Message
.operate_type == 'product_update') {
await goodsInit()
await cartsGoodsInfoInit()
}
// 提示
if (Message.status == 0 && Message.type != 'no_suit_num') {
uni.showToast({
title: Message.msg,
icon: "none"
})
}
if (Message.type == 'no_suit_num') {
// console.log(specifications)
uni.showModal({
title: '提示',
showCancel: false,
content: '此商品库存不足起售数量!',
success: async (data) => {
}
});
}
}
}
//购物车数据
const carts = ref([])
//历史订单数据
const oldOrder = ref({
detailMap: {},
originAmount: 0
})
// 会员信息
const orderVIP = ref(uni.cache.get('orderVIP') || {
isVip: false
})
function updateData(key, newval) {
if (key === 'orderVIP') {
uni.cache.set('orderVIP', newval)
return orderVIP.value = newval
}
if (key === 'shopInfo') {
uni.cache.set('shopInfo', newval)
return shopInfo.value = newval
}
}
// 商品订单会员
const shopInfo = ref(uni.cache.get('shopInfo') || {
isMemberPrice: 0
})
//是否使用会员价
const useVipPrice = computed(() => {
const isUse=(orderVIP.value.isVip && shopInfo.value.isMemberPrice)?true:false
console.log('useVipPrice',isUse);
return isUse
})
function currentCalcMpneyNumber(item){
const n=item.number-(item.returnNum||0)
return n<=0?0:n
}
//历史订单商品价格总和
const oldOrderMoney = computed(() => {
let total = 0
for (let i in oldOrder.value.detailMap) {
total += oldOrder.value.detailMap[i].reduce((prve, cur) => {
if (cur.isGift) {
return prve + 0
}
const discount_sale_amount = cur.discount_sale_amount * 1 || 0
const memberPrice = cur.skuData ? (cur.skuData.memberPrice || cur.skuData
.salePrice) : 0
const price = (discount_sale_amount || cur.salePrice || 0)
const number =currentCalcMpneyNumber(cur)
return prve + (number <= 0 ? 0 : number) * (discount_sale_amount || (useVipPrice
.value ? memberPrice : price))
}, 0)
}
return total
})
//当前购物车总价格
const totalPrice = computed(() => {
const money = carts.value.reduce((prve, cur) => {
const memberPrice = cur.memberPrice || cur.salePrice
const price = useVipPrice.value ? memberPrice : cur.salePrice;
const curMoney = price * currentCalcMpneyNumber(cur)
return prve + curMoney
}, 0)
return money
})
// 霸王餐购物车原价,不享受任何优惠
const totalOriginPrice = computed(() => {
const money = carts.value.reduce((prve, cur) => {
const curMoney = cur.salePrice * currentCalcMpneyNumber(cur)
return prve + curMoney
}, 0)
return money
})
//商品券抵扣金额
// const productCouponDiscountAmount = computed(() => {
// let index = -1;
// return quansSelArr.value.reduce((pre, cur) => {
// index++;
// return pre + returnProDiscount(cur, index) * 1;
// }, 0);
// });
//返回打包数量称重商品打包数量最大为1
function returnCartPackNumber(cur) {
const maxReturnNum = cur.number - (cur.returnNum || 0);
let pack_number = cur.number;
pack_number = (cur.product_type == 'weight' && pack_number > 1) ? 1 : pack_number;
pack_number = Math.min(maxReturnNum, pack_number);
pack_number = pack_number <= 0 ? 0 : pack_number
return pack_number * 1
}
//当前购物车打包费
const totalPackFee = computed(() => {
const money = carts.value.reduce((prve, cur) => {
const curMoney = (cur.packFee || 0) * currentCalcMpneyNumber(cur)
return prve + curMoney
}, 0)
return money
})
//打包费
const packFee = computed(() => {
const nowPackFee = carts.value.reduce((acc, cur) => {
return acc + (cur.packFee || 0) * returnCartPackNumber(cur)
}, 0)
let oldPackfee = 0;
for (let i in oldOrder.value.detailMap) {
oldPackfee += oldOrder.value.detailMap[i].reduce((prve, cur) => {
return prve + (cur.packFee || 0) * returnCartPackNumber(cur)
}, 0)
}
return nowPackFee + oldPackfee
})
//购物车是否为空
const isEmpty = computed(() => {
return !carts.value || carts.value.length <= 0
})
// 计算向上取整
const roundUpToTwoDecimals = (num, i) => {
// 先将数字乘以 100 并转换为字符串保留足够的小数位
let temp = (num * 100).toFixed(10);
// 向上取整
let rounded = null
if (i == 'upward') {
rounded = Math.ceil(parseFloat(temp));
} else {
rounded = Math.floor(parseFloat(temp));
}
// 再除以 100 得到保留两位小数的结果
return rounded / 100;
};
// 精确计算函数
const preciseCalculation = (num1, num2) => {
// 将数字转换为整数,乘以 100 以保留两位小数
const int1 = BigInt(Math.round(num1 * 100));
const int2 = BigInt(Math.round(num2 * 100));
// 执行乘法运算
const result = int1 * int2;
// 再除以 10000 以还原为原来的小数
return Number(result) / 10000;
};
// 计算购物车商品总价格
const getTotalTotalPrices = (matchedProducts, isBwc = true) => {
if (!matchedProducts || !Array.isArray(matchedProducts)) {
return 0;
}
// console.log(uni.cache.get('orderVIP').isVip, uni.cache.get('ordershopUserInfo').isMemberPrice,
// 111)
// 购物车总数价格
console.log('isBwc');
console.log(isBwc);
let cart = matchedProducts.reduce((total, item) => {
if(isBwc===true){
return total + (parseFloat(item.price) * parseFloat(item.num - item.returnNum));
}
// 是否启用会员价 0否1是
if (useVipPrice.value) {
// memberPrice会员价
return total + (parseFloat(item.memberPrice || item.price) * parseFloat(item
.num - item
.returnNum));
} else {
// salePrice销售价
return total + (parseFloat(item.price) * parseFloat(item.num - item.returnNum));
}
}, 0);
cart = cart.toFixed(2)
console.log(parseFloat(cart))
// 向上取整并保留两位小数
// let result = roundUpToTwoDecimals(cart, 'upward')
return parseFloat(cart);
};
// 计算商品卷所选择的总价格
const getTotalProductroll = (matchedProducts) => computed(() => {
if (!matchedProducts || !Array.isArray(matchedProducts)) {
return 0;
}
// 购物车总数价格
let cart = matchedProducts.reduce((total, item) => {
// 是否启用会员价 0否1是
if (useVipPrice.value) {
// memberPrice会员价
return total + parseFloat(item.memberPrice || item.price)
} else {
// salePrice销售价
return total + parseFloat(item.price)
}
}, 0);
// 向上取整并保留两位小数
let result = roundUpToTwoDecimals(cart, 'upward')
return result;
});
// 桌位置
const getTotalSeatcharge = (seatNum) => computed(() => {
// 是否免除桌位费 0 否 1 是
let cart = 0
if (uni.cache.get('ordershopUserInfo').isTableFee == 0 && seatNum) {
cart = parseFloat(seatNum) * parseFloat(uni.cache.get('ordershopUserInfo').tableFee)
// Math.ceil(parseFloat(seatNum) * parseFloat(
// uni.cache.get('ordershopUserInfo').tableFee) * 100) / 100;
}
// 向下取整并保留两位小数
let result = roundUpToTwoDecimals(cart, 'downward')
return result;
});
// 计算购物车总打包费用(向下取整并保留两位小数)
const getTotalPackFee = (cartList) => computed(() => {
const total = cartList.reduce((sum, item) => {
return sum + (parseFloat(item.packAmount) * (parseFloat(item.packNumber) || (
parseFloat(item.num) - parseFloat(item.returnNum))));
}, 0);
// 向下取整并保留两位小数
let result = roundUpToTwoDecimals(total, 'downward')
return result;
// return Math.floor(total * 100) / 100;
});
//获取热门商品
async function getHotProduct() {
}
//获取分组商品
async function getGroupProduct() {
}
return {
getTotalPackFee,
getTotalSeatcharge,
getTotalTotalPrices,
getTotalProductroll,
carts,
isEmpty,
setGoodsMap,
goodsIsloading,
goodsInit,
onMessage,
totalPrice,
totalPackFee,
updateData,
useVipPrice,totalOriginPrice
};
}
);

821
stores/carts.ts Normal file
View File

@@ -0,0 +1,821 @@
import { computed, reactive, ref } from "vue";
import WebSocketManager, { type ApifoxModel, msgType } from "@/common/js/carts-websocket";
import {
productminiApphotsquery,
APIgroupquery,
APIminiAppinfo,
APIminiAppskuinfo
} from "@/common/api/product/product.js";
const shopInfo = uni.cache.get('shopInfo') || { isMemberPrice: false }
export interface CartsState {
id : string | number;
[property : string] : any;
}
import { defineStore } from 'pinia'
import { isProductAvailable, goodsInitParamsFull } from '@/utils/product.js'
function ElMessage() {
}
export const useCartsStore = defineStore("carts", () => {
//选择用户
const vipUser = ref<{ id ?: string | number, isVip ?: boolean }>(uni.cache.get('orderVIP') || {});
function changeUser(user : any) {
vipUser.value = user;
}
//就餐类型 dine-in take-out
let dinnerType = ref<string>('dine-in');
//是否启用会员价
const useVipPrice = computed(() => {
return (shopInfo.isMemberPrice && vipUser.value.id && vipUser.value.isVip) ? true : false
})
//台桌id
// const table_code = useStorage('Instead_table_code', '');
const table_code = ref('')
//购物车是否初始化连接加载完成
const isLinkFinshed = ref(false)
//当前购物车数据
const list = ref<any[]>([]);
//历史订单数据
// const oldOrder = useStorage<any>("Instead_olold_order", {
// detailMap: [],
// originAmount: 0
// });
const oldOrder = ref({
detailMap: [],
originAmount: 0
})
//代客下单页面全部商品缓存,包括招牌菜和分组
const goods = ref([])
const hotgoods = ref([])
const groupGoods = ref([])
async function getGoods(query : any) {
// 招牌菜
const hotsRes = await productminiApphotsquery({
...query,
});
//分组
const groupGoodsRes = await APIgroupquery({
...query,
});
hotgoods.value = hotsRes.map(product => {
return goodsInitParamsFull(product)
})
groupGoods.value = groupGoodsRes.map(v => {
return {
...v,
productList: v.productList.map(product => {
return goodsInitParamsFull(product)
})
}
})
goods.value = [...hotsRes, ...groupGoodsRes.reduce((prve, cur) => {
prve.push(...cur.productList)
return prve
}, [])]
setGoodsMap(goods.value)
}
function setGoodsMap(goods : any[]) {
for (let item of goods) {
goodsMap[item.id] = item;
}
}
//赠菜
const giftList = ref([])
let goodsMap : { [key : string] : any } = reactive({});
//当前选中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]
}
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
})
function returnOneGoodsTotalMoney(cur : any) {
const memberPrice = cur.memberPrice || cur.salePrice
const total = cur.number * (useVipPrice.value ? memberPrice : cur.salePrice)
if (cur.type == 'weight') {
return customTruncateToTwoDecimals(total)
} else {
return total
}
}
//赠菜总价
const giftMoney = computed(() => {
let oldGiftMoney = 0
for (let i in oldOrder.value.detailMap) {
oldGiftMoney += oldOrder.value.detailMap[i].filter((v : any) => v.isGift).reduce((prve : number, cur : any) => {
const memberPrice = cur.memberPrice || cur.salePrice
return prve + cur.number * (useVipPrice.value ? memberPrice : cur.salePrice)
}, 0)
}
const nowTotal = giftList.value.reduce((acc : number, cur : any) => {
const memberPrice = cur.memberPrice || cur.salePrice
return acc + cur.number * (useVipPrice.value ? memberPrice : cur.salePrice)
}, 0)
return (nowTotal + oldGiftMoney)
})
//返回打包数量称重商品打包数量最大为1
function returnCartPackNumber(cur : any) {
const maxReturnNum = cur.number - (cur.returnNum || 0);
let pack_number = (dinnerType.value == 'take-out' ? cur.number : cur.pack_number * 1);
pack_number = (cur.product_type == 'weight' && pack_number > 1) ? 1 : pack_number;
pack_number = Math.min(maxReturnNum, pack_number);
pack_number = pack_number <= 0 ? 0 : pack_number
return pack_number * 1
}
//打包数量
const packNum = computed(() => {
const nowCartNumber = list.value.reduce((acc : number, cur : any) => {
return acc + returnCartPackNumber(cur)
}, 0)
const giftNumber = giftList.value.reduce((acc : number, cur : any) => {
return acc + returnCartPackNumber(cur)
}, 0)
let oldNumber = 0
for (let i in oldOrder.value.detailMap) {
oldNumber += oldOrder.value.detailMap[i].reduce((prve : number, cur : any) => {
return prve + returnCartPackNumber(cur)
}, 0)
}
return nowCartNumber + giftNumber + oldNumber
})
//打包费
const packFee = computed(() => {
const nowPackFee = list.value.reduce((acc : number, cur : any) => {
return acc + (cur.packFee || 0) * returnCartPackNumber(cur)
}, 0)
const giftPackFee = giftList.value.reduce((acc : number, cur : any) => {
return acc + (cur.packFee || 0) * returnCartPackNumber(cur)
}, 0)
let oldPackfee = 0;
for (let i in oldOrder.value.detailMap) {
oldPackfee += oldOrder.value.detailMap[i].reduce((prve : number, cur : any) => {
return prve + (cur.packFee || 0) * returnCartPackNumber(cur)
}, 0)
}
return nowPackFee + giftPackFee + oldPackfee
})
//会员优惠
const vipDiscount = computed(() => {
if (!useVipPrice.value) {
return 0
}
const listTotal = list.value.reduce((acc : number, cur : any) => {
const n = (cur.salePrice * 1 - cur.memberPrice * 1) * cur.number
return acc + (n <= 0 ? 0 : n)
}, 0)
const giftTotal = giftList.value.reduce((acc : number, cur : any) => {
const n = (cur.salePrice * 1 - cur.memberPrice * 1) * cur.number
return acc + (n <= 0 ? 0 : n)
}, 0)
let oldTotal = 0;
for (let i in oldOrder.value.detailMap) {
oldTotal += oldOrder.value.detailMap[i].reduce((prve : number, cur : any) => {
const n = (cur.salePrice * 1 - cur.memberPrice * 1) * cur.number
return prve + (n <= 0 ? 0 : n)
}, 0)
}
return listTotal + giftTotal + oldTotal
})
//单品改价优惠
const singleDiscount = computed(() => {
const listTotal = list.value.reduce((acc : number, cur : any) => {
if (cur.discount_sale_amount * 1 <= 0) {
return acc + 0
}
const originPrice = useVipPrice.value ? (cur.memberPrice || cur.salePrice) : cur.salePrice
const n = (originPrice * 1 - cur.discount_sale_amount * 1) * cur.number
return acc + (n <= 0 ? 0 : n)
}, 0)
return listTotal
})
// 优惠
const yiyouhui = computed(() => {
const youhui = giftMoney.value * 1 + vipDiscount.value * 1 + singleDiscount.value * 1
if (youhui > 0) {
return '已优惠¥' + youhui.toFixed(2)
}
return ''
})
//历史订单价格
const oldOrderMoney = computed(() => {
let total = 0
for (let i in oldOrder.value.detailMap) {
total += oldOrder.value.detailMap[i].reduce((prve : number, cur : any) => {
if (cur.isGift) {
return prve + 0
}
const discount_sale_amount = cur.discount_sale_amount * 1 || 0
const memberPrice = cur.skuData ? (cur.skuData.memberPrice || cur.skuData.salePrice) : 0
const price = (discount_sale_amount || cur.salePrice || 0)
const number = (cur.number - cur.returnNum)
return prve + (number <= 0 ? 0 : number) * (discount_sale_amount || (useVipPrice.value ? memberPrice : price))
}, 0)
}
return total
})
//支付总价
const payMoney = computed(() => {
const money = list.value.reduce((acc : number, cur : any) => {
const discount_sale_amount = cur.discount_sale_amount * 1 || 0
const memberPrice = cur.skuData ? (cur.skuData.memberPrice || cur.skuData.salePrice) : 0
const price = (cur.discount_sale_amount * 1 || cur.salePrice || 0)
return acc + cur.number * (discount_sale_amount || (useVipPrice.value ? memberPrice : price))
}, 0)
return (money + packFee.value * 1 + oldOrderMoney.value * 1).toFixed(2)
})
//只算商品的总价
const goodsTotal = computed(() => {
const money = list.value.reduce((acc : number, cur : any) => {
const discount_sale_amount = cur.discount_sale_amount * 1 || 0
const memberPrice = cur.skuData ? (cur.skuData.memberPrice || cur.skuData.salePrice) : 0
const price = (cur.discount_sale_amount * 1 || cur.salePrice || 0)
return acc + cur.number * (discount_sale_amount || (useVipPrice.value ? memberPrice : price))
}, 0)
return (money + oldOrderMoney.value * 1).toFixed(2)
})
//总计数量
const totalNumber = computed(() => {
const cartNumber = list.value.reduce((acc : number, cur : any) => {
return acc + cur.number * 1
}, 0)
const giftNumber = giftList.value.reduce((acc : number, cur : any) => {
return acc + cur.number * 1
}, 0)
let oldNumber = 0
for (let i in oldOrder.value.detailMap) {
oldNumber += oldOrder.value.detailMap[i].reduce((prve : number, cur : any) => {
return prve + cur.number * 1
}, 0)
}
return cartNumber + giftNumber + oldNumber
})
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 * 1;
if (dinnerType.value == 'take-out') {
pack_number = newNumber
}
if (item.product_type == 'weight' && item.pack_number * 1 >= 1) {
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
}
selPlaceNum.value = -1;
isOldOrder.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: '',
suitNum: 1
}
//当前购物车直接添加
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 {
console.log(msg);
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 clearHistory() {
sendMessage('clearOrder', {});
}
function del(data : any) {
sendMessage('del', { id: data.id });
}
function update(data : any) {
console.log(data);
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, suitNum, 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);
}
console.log(key, val)
if (key == 'discount_sale_amount' && val * 1 <= 0) {
// ElMessage.error('价格不能为0')
return
}
const msg = { ...cart, [key]: val }
if (key == 'number' && dinnerType.value == 'take-out') {
msg.pack_number == val
msg.suitNum == skuData.suitNum
}
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 = {}
}
function nowCartsClear() {
if (selPlaceNum.value == 1) {
selListIndex.value = -1;
}
list.value = [];
giftList.value = [];
}
// 寻找套餐商品sku
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 })
console.log('getOldOrder');
console.log(res);
if (res) {
setOldOrder(res)
} else {
oldOrder.value = {
detailMap: [],
originAmount: 0
}
}
}
function getProductDetails(v : { product_id : string, sku_id : string }) {
const goods = goodsMap[v.product_id]
if (!goods) {
return undefined
}
let skuData = undefined;
skuData = goods?.skuList.find((sku : { id : string, salePrice : number }) => sku.id == v.sku_id);
// if (goods.type == 'package') {
// //套餐商品
// const SnapSku = findInGroupSnapSku(goods.groupSnap, v.sku_id)
// skuData = { ...SnapSku, salePrice: SnapSku ? SnapSku.price : 0 }
// } else {
// skuData = goods?.skuList.find((sku: { id: string, salePrice: number }) => sku.id == v.sku_id);
// }
skuData = goods?.skuList.find((sku : { id : string, salePrice : number }) => sku.id == v.sku_id);
if (skuData) {
return {
salePrice: skuData ? skuData.salePrice : 0,
memberPrice: skuData ? skuData.memberPrice : 0,
coverImg: goods.coverImg,
name: goods.name,
specInfo: skuData.specInfo,
packFee: goods.packFee || 0,
type: goods.type,
skuData
}
} else {
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 })
console.log(skuData)
console.log(v)
return {
...v,
...skuData,
skuData: {
...skuData,
salePrice: v.price,
memberPrice: v.memberPrice
},
placeNum: v.placeNum,
number: v.num,
id: v.id,
salePrice: v.price,
memberPrice: v.memberPrice,
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,
packFee: v.packAmount,
}
})
}
console.log('newData', newData)
return newData
}
function setOldOrder(data : any) {
oldOrder.value = {
...data,
detailMap: returnDetailMap(data.detailMap)
}
}
let $initParams = {} as ApifoxModel
/**
*
* @param initParams 购物车初始化参数
* @param $goodsMap 商品id对应的map
* @param oldOrder 历史订单数据
*/
async function init(initParams : ApifoxModel, $oldOrder : any | undefined) {
// 商品id对应的数据map
await getGoods({})
if ($oldOrder) {
setOldOrder($oldOrder)
} else {
oldOrder.value = { detailMap: [], originAmount: 0 }
}
// console.log('oldOrder.detailMap', oldOrder.value.detailMap)
// const cache_table_code = localStorage.getItem('cache_table_code');
// const randomTableCode = cache_table_code ? cache_table_code : ('APC' + (1000 + Math.floor(Math.random() * 9000)))
if (initParams) {
initParams.table_code = initParams.table_code ? initParams.table_code : ''
table_code.value = initParams.table_code
$initParams = initParams;
}
console.log($initParams)
// localStorage.setItem('cache_table_code', table_code.value);
concocatSocket($initParams)
}
function concocatSocket(initParams = $initParams) {
console.log("初始化参数", initParams);
WebSocketManager.subscribeToTopic(initParams, (msg) => {
console.log("收到消息:", msg);
if (msg.hasOwnProperty('status') && msg.status != 1) {
if (msg.type === 'no_suit_num' && selListIndex.value != -1) {
return uni.showModal({
title: '提示',
showCancel: false,
content: `${list.value[selListIndex.value].name}库存不足`,
success() {
if (res.confirm) {
list.value.splice(selListIndex.value, 1)
}
}
})
}
uni.showToast({
title: msg.message || msg.msg || '操作失败',
icon: 'none'
})
return
}
if (msg && msg.data) {
if (Array.isArray(msg.data) && msg.data.length && msg.data[0].table_code) {
table_code.value = msg.data[0].table_code
}
if (msg.data.table_code) {
table_code.value = table_code.value ? table_code.value : msg.data.table_code
}
if (msg.table_code) {
table_code.value = table_code.value ? table_code.value : msg.table_code
}
}
// 初始化
if (msg.operate_type === "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) as (keyof typeof 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) as (keyof typeof skuData)[]).forEach((key) => {
v[key] = skuData[key];
});
} else {
del({ id: v.id })
return false
}
return v.is_gift
})
}
//广播
if (msg.type === "bc") {
msg.operate_type = 'shopping_' + msg.operate_type
}
if (msg.operate_type === "add") {
if (list.value.find(v => v.id == msg.data.id)) {
console.error('该商品已存在')
// ElMessage.warning(msg.message || '该商品已存在')
return
}
const skuData = getProductDetails({ product_id: msg.data.product_id, sku_id: msg.data.sku_id })
if (skuData || msg.data.is_temporary) {
const newGoods = { ...skuData, ...msg.data }
console.log('新增商品', newGoods)
list.value.push(newGoods)
console.log('添加成功')
// ElMessage.success(msg.message || '添加成功')
return
}else{
console.error('未找到对应商品');
}
}
if (msg.operate_type === "edit") {
const newCart = msg.data
const index = list.value.findIndex((item) => item.id === newCart.id)
const giftIndex = giftList.value.findIndex((item) => item.id === newCart.id)
const cartItem = list.value[index] || { is_gift: false };
const giftItem = giftList.value[giftIndex];
if (giftItem) {
//操作赠菜
if (!newCart.is_gift) {
giftList.value.splice(giftIndex, 1)
list.value.push({ ...giftItem, ...newCart })
selListIndex.value = -1
} else {
giftList.value[giftIndex] = { ...giftItem, ...newCart }
}
}
if (cartItem) {
//操作非赠菜
if (newCart.is_gift) {
list.value.splice(index, 1)
giftList.value.push({ ...cartItem, ...newCart })
selListIndex.value = -1
} else {
list.value[index] = { ...cartItem, ...newCart }
}
}
// ElMessage.success(msg.message || '修改成功')
}
if (msg.operate_type === "del") {
const cartId = Array.isArray(msg.data) ? msg.data[0].id : msg.data.id
const listIndex = list.value.findIndex((item) => item.id == cartId)
if (listIndex > -1) {
list.value.splice(listIndex, 1)
}
const giftIndex = giftList.value.findIndex((item) => item.id == cartId)
if (giftIndex > -1) {
giftList.value.splice(giftIndex, 1)
}
// ElMessage.success(msg.message || '删除成功')
return
}
if (msg.operate_type === "cleanup") {
nowCartsClear()
getOldOrder(msg.data.table_code)
}
if (msg.operate_type === "batch") {
concocatSocket({ ...$initParams, table_code: table_code.value })
}
if (msg.operate_type === "clearOrder") {
getOldOrder(msg.data.table_code)
}
if (msg.operate_type === "product_update") {
console.log('商品更新')
init($initParams, oldOrder.value)
}
});
}
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: operate_type, table_code: table_code.value }
for (let i in delArr) {
delete msg[delArr[i]]
}
console.log('send msg', msg)
WebSocketManager.sendMessage(msg);
}
return {
clearHistory,
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,
hotgoods, groupGoods
};
});

View File

@@ -214,7 +214,7 @@ export const productStore = defineStore('product', {
uni.cache.set('shopUserInfo', res);
uni.cache.set('orderVIP', res)
uni.cache.set('ordershopUserInfo', res.shopInfo)
resolve(true)
resolve(res)
} catch (e) {
reject(false)
}
@@ -226,14 +226,16 @@ export const productStore = defineStore('product', {
actionsAPIuser() {
return new Promise(async (resolve, reject) => {
try {
let res=null
// 获取店铺用户会员信息
if (uni.cache.get('shopId')) {
this.actionsproductqueryProduct()
res = await this.actionsproductqueryProduct()
} else {
let res = await APIuser()
res = await APIuser()
uni.cache.set('userInfo', res);
}
resolve(true)
console.log('actionsAPIuser res',res);
resolve(res)
} catch (e) {
reject(false)
}

47
utils/product.js Normal file
View File

@@ -0,0 +1,47 @@
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween'
dayjs.extend(isBetween)
// 判断商品是否在可售时间内
export const isProductAvailable = (sellDaysStr, startTimeStr, endTimeStr) => {
// 将后端返回的字符串转换为数组
const sellDays = sellDaysStr.split(',');
const now = dayjs();
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const currentDay = days[now.day()];
// console.log('当前日期:', currentDay);
// console.log('可售日期列表:', sellDays);
// 检查当前周几是否在可售周几列表中
if (!sellDays.includes(currentDay)) {
// console.log('当前日期不在可售日期列表中');
return false;
}
const startTime = dayjs(`${now.format('YYYY-MM-DD')} ${startTimeStr}`);
let endTime = dayjs(`${now.format('YYYY-MM-DD')} ${endTimeStr}`);
// 处理跨天情况
if (endTime.isBefore(startTime)) {
endTime = endTime.add(1, 'day');
}
// console.log('当前时间:', now.format('YYYY-MM-DD HH:mm:ss'));
// console.log('开始时间:', startTime.format('YYYY-MM-DD HH:mm:ss'));
// console.log('结束时间:', endTime.format('YYYY-MM-DD HH:mm:ss'));
const isInRange = now.isBetween(startTime, endTime, null, '[)');
// console.log('当前时间是否在可售时间范围内:', isInRange);
return isInRange;
}
//点单页面商品数据补充格式化,增加购物车选中数量,增加是否在可售时段内
export function goodsInitParamsFull(product) {
return {
...product,
cartNumber: 0,
isSaleTimeshow: isProductAvailable(product.days, product
.startTime, product.endTime)
}
}