菜大王uniapp开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

899 lines
22 KiB

<script setup>
import { onLoad, onShow } from "@dcloudio/uni-app";
import { ref } from "vue";
import customTabBar from "@/components/custom-tab-bar/my-tab-bar.vue";
import keyboard from "@/components/keyboard/keyboard.vue";
import navv from "@/components/nav/nav.vue";
// import topTitle from "@/components/topTitle/topTitle.vue";
import {
addCartApi,
deleteFromCartApi,
editDefaultAddressApi,
getCartInfoApi,
getMyAreaApi,
getUserInfoApi,
previewApi,
} from "@/libs/api";
import { sleep, validates } from "@/libs/utils";
import useStore from "@/store";
const store = useStore();
/**
* 选择收货地址
*/
const selectAddress = ref({});
/**
* 购物车是否完成
*/
const finished = ref(false);
/**
* 购物车列表
*/
const cartLists = ref([]);
/**
* 服务费用
*/
const serviceFee = ref(0);
/**
* 配送费
*/
const shippingFee = ref(0);
/**
* 购物车总价
*/
const totalPrice = ref(0);
/**
* 是否生成订单
*/
// const genOrder = ref(false);
/**
* 选中的购物车项
*/
// const checkedItems = ref([]);
/**
* 选中所有购物车项
*/
const checkedAll = ref(false);
/**
* 购物车项数量
*/
const cartCount = ref(0);
/**
* 加载中
*/
// const isLoading = ref(false);
/**
* 是否显示购物车小球
*/
const showBall = ref(false);
/**
* 是否显示键盘
*/
const isShowkeyboard = ref(false);
/**
* 购物车小球的动画
*/
const actionTime = ref(true);
/**
* 购物车小球的动画
*/
const animationY = ref({});
/**
* 购物车小球的X轴动画
*/
const animationX = ref({});
/**
* 购物车小球的Y轴坐标
*/
const testY = ref(0);
/**
* 购物车小球的X轴坐标
*/
const testX = ref(0);
/**
* 触摸开始时的 X 坐标
*/
const startX = ref(0);
/**
* 触摸开始时的 Y 坐标
*/
const startY = ref(0);
/**
* 底部导航栏高度
*/
// const tabbarHeight = ref(0)
/**
* 点击选择收货地址
*/
function onGoSelectAddress() {
uni.navigateTo({
url: "/pages/deliveryAddress/deliveryAddress",
});
}
/**
* 点击去逛逛
*/
function onGotoAlls() {
uni.switchTab({
url: "/pages/allDish/allDish",
});
}
/**
* 点击管理购物车 编辑和完成
*/
function onManageCart() {
finished.value = !finished.value;
}
/**
* 计算滑动角度
* @param {object} start 起点坐标
* @param {object} end 终点坐标
*/
function angle(start, end) {
// 返回角度 /Math.atan()返回数字的反正切值
const _X = end.X - start.X;
const _Y = end.Y - start.Y;
return 360 * Math.atan(_Y / _X) / (2 * Math.PI);
}
/**
* 购物车项触摸开始事件
* @param e 触摸事件对象
*/
function onTouchStart(e) {
cartLists.value = cartLists.value.map(item => ({ ...item, isTouchMove: false }));
startX.value = e.changedTouches[0].clientX;
startY.value = e.changedTouches[0].clientY;
}
/**
* 购物车项触摸移动事件
* @param e 触摸事件对象
* @param index 购物车项索引
*/
function onTouchMove(e, index) {
const touchMoveX = e.changedTouches[0].clientX; // 滑动变化坐标
const touchMoveY = e.changedTouches[0].clientY; // 滑动变化坐标
const angleVal = angle({ X: startX.value, Y: startY.value }, { X: touchMoveX, Y: touchMoveY });
if (Math.abs(angleVal) > 30) {
return;
}
// 左滑
const isLeft = touchMoveX <= startX.value;
cartLists.value[index].isTouchMove = isLeft;
}
/**
* 购物车项选择事件
* @param item 购物车项
*/
async function onCheckboxChange(item) {
// cartLists.value[index].checked = !cartLists.value[index].checked;
item.checked = !item.checked;
store.changeCartList(cartLists.value.filter(item => item.checked));
const checkedItems = store.cartList.map(item => item.id);
await preview(checkedItems);
inspectCheck();
}
/**
* 确定购买数量
* @param value 购买数量
* @param item 商品项
*/
async function determine(value, item) {
const isPass = validates([
() => item.stock != -1 && value > item.stock && "采购数量不能大于库存数量",
() => item.stock == 0 && "库存数量为0无法添加",
]);
if (!isPass) {
return;
}
item.sum = value;
isShowkeyboard.value = false;
const data = {
quantity: value,
specId: item.id,
Chuxiao: item.chuxiao,
warehouseId: uni.getStorageSync("warehousId"),
addrId: uni.getStorageSync("addressId"),
isUpdate: 1,
isChuxiao: false,
};
if (!data.specId) {
uni.showModal({
title: "提示",
content: "当前商品规格错误,请稍候再试",
showCancel: false,
confirmText: "确定",
});
}
if (!data.warehouseId || !data.addrId) {
uni.showModal({
title: "提示",
content: "请先选择收货地址,再添加商品",
showCancel: false,
confirmText: "确定",
});
}
const res = await addCartApi(data);
if (res.code !== "0") {
return;
}
// store.changeCartList(res.data);
initCartInfo();
}
/**
* 购物车项增加事件
* @param e 事件对象
* @param item 购物车项
// * @param index 购物车项索引
*/
function onAdd(e, item) {
if (!actionTime.value) {
return;
}
actionTime.value = false;
testX.value = e.detail.x;
testY.value = e.detail.y;
createAnimation(e.detail.x, e.detail.y);
// const isPass = validates([
// () => item.stock == item.sum && "采购数量不能大于库存数量",
// () => item.stock == 0 && "库存数量为0无法添加",
// ]);
// if (!isPass) {
// return;
// }
// 判空为 0, 否则转换为数字
item.quantity = Number(item.quantity) || 0;
// 起订量(item.minNum), 否则每次+1
const diff = item.quantity === 0 ? (item.minNum || 1) : 1;
item.quantity += diff;
toCart(item, diff);
}
/**
* 创建购物车小球的动画
*/
async function createAnimation(eX, eY) {
const bottomX = (store.tabbarItemWidth || 83) * 2 - 30;
const bottomY = uni.getWindowInfo().windowHeight;
const stepX = flyX(bottomX, eX);
const stepY = flyY(bottomY, eY);
showBall.value = true;
animationX.value = stepX.export(); // 创建小球水平动画
animationY.value = stepY.export(); // 创建小球垂直动画
await sleep(400);
actionTime.value = true;
showBall.value = false;
animationX.value = flyX(0, 0).export();
animationY.value = flyY(0, 0).export();
// await sleep(800);
}
/**
* 购物车小球水平移动动画
*/
function flyX(bottomX, ballX, duration = 400) {
const animation = uni.createAnimation({
duration,
timingFunction: "linear",
});
animation.translateX(-bottomX).step();
return animation;
}
/**
* 购物车小球垂直移动动画
*/
function flyY(bottomY, ballY, duration = 400) {
const animation = uni.createAnimation({
duration,
timingFunction: "ease-in",
});
animation.translateY(bottomY - ballY).step();
return animation;
}
/**
* 购物车项减少事件
* @param e 事件对象
* @param item 购物车项
// * @param index 购物车项索引
*/
function onMinus(e, item) {
// 判空为 0, 否则转换为数字
item.quantity = Number(item.quantity) || 0;
// 达到起订量(item.minNum)直接清空, 否则每次-1
const minNum = item.minNum || 1;
const diff = -(item.quantity === minNum ? minNum : 1);
item.quantity += diff;
toCart(item, diff);
}
/**
* 删除一件
*/
function onDelete(index) {
uni.showModal({
title: "警告",
content: "确定删除当前商品?",
confirmColor: "#3cc51f",
async success(res) {
if (res.cancel) {
return;
}
const res2 = await deleteFromCartApi({
ids: cartLists.value[index].id,
});
if (res2.code !== "0") {
return;
}
cartLists.value.splice(index, 1);
initCartInfo();
},
});
}
/**
* 全部反选
*/
async function onSelectAllGoods() {
checkedAll.value = !checkedAll.value;
cartLists.value = cartLists.value.map(item => ({ ...item, checked: checkedAll.value }));
// cartLists.value[index].checked = !cartLists.value[index].checked;
store.changeCartList(cartLists.value.filter(item => item.checked));
const checkedItems = store.cartList.map(item => item.id);
await preview(checkedItems);
inspectCheck();
}
/**
* 结算
*/
function onGotoOrder() {
if (store.cartList.length == 0) {
uni.showToast({
title: "下单商品不能为空",
icon: "none",
duration: 3000,
});
return;
}
let replenish = ""; // 补货
let remove = ""; // 下架
cartLists.value.forEach((item) => {
if (item.checked) {
if (item.type == 2) {
remove += `${item.productName}`;
}
if (item.type == 3) {
replenish += `${item.productName}`;
}
}
});
if (remove.length > 0) {
uni.showModal({
title: "提示",
content: `${remove.slice(0, -1)}已下架`,
showCancel: false,
});
return;
}
if (replenish.length > 0) {
uni.showModal({
title: "提示",
content: `${replenish.slice(0, -1)}缺货`,
showCancel: false,
});
return;
}
uni.navigateTo({
// url: "./order/order",
});
}
/**
* 删除所选
*/
function onDeleteCart() {
uni.showModal({
title: "警告",
content: "确定删除选中商品?",
confirmColor: "#3cc51f",
async success(res) {
if (res.cancel) {
return;
}
const checkedItems = store.cartList.filter(item => item.checked).map(item => item.id);
const res2 = await deleteFromCartApi({
ids: checkedItems.join(","),
});
if (res2.code !== "0") {
return;
}
cartLists.value = cartLists.value.filter(item => !checkedItems.includes(item.id));
initCartInfo();
},
});
// uni.showToast({
// title: "删除所选商品",
// icon: "none",
// });
}
/**
* 更新购物车
* @param item 商品项
* @param diff 变化量
*/
async function toCart(item, diff) {
const data = {
quantity: diff,
specId: item.specId,
// Chuxiao: item.chuxiao,
isChuxiao: false,
warehouseId: uni.getStorageSync("warehousId"),
addrId: uni.getStorageSync("addressId"),
isUpdate: 0,
// isChuxiao: item.chuxiao,
};
if (!data.specId) {
uni.showModal({
title: "提示",
content: "当前商品规格错误,请稍候再试",
showCancel: false,
confirmText: "确定",
});
}
if (!data.warehouseId || !data.addrId) {
uni.showModal({
title: "提示",
content: "请先选择收货地址,再添加商品",
showCancel: false,
confirmText: "确定",
});
}
// keys.push(item.id);
const res = await addCartApi(data);
// keys.splice(item.id, 1);
if (res.code !== "0") {
return;
}
// store.changeCartList(res.data)
initCartInfo();
}
/**
* 获取仓库列表数据
*/
async function getMyArea() {
const res = await getMyAreaApi({
warehouseid: "",
isEnabled: 1,
});
if (res.code !== "0")
return;
if (!res.data.login) {
uni.navigateTo({
url: "/pages/login/login",
});
return;
}
if (!res.data.list.length) {
uni.showModal({
content: "请先添加收货地址",
confirmColor: "#3aa24b",
confirmText: "去添加",
success(res) {
if (res.confirm) {
uni.navigateTo({
// url: "../../address/pages/addressLists/addressLists",
});
}
},
});
return;
}
// each 就是找到有 isLastDefaultAddr,没有就那第一个 isDefault 最大值
res.data.list = res.data.list.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
selectAddress.value = res.data.list.find(each => each.isLastDefaultAddr) || res.data.list[0];
uni.setStorageSync("warehousId", selectAddress.value.warehousId);
uni.setStorageSync("addressId", selectAddress.value.addrId);
if (selectAddress.value.addrId) {
editDefaultAddress();
}
else {
uni.showModal({
content: "请先添加收货地址",
confirmColor: "#3aa24b",
confirmText: "去添加",
success(res) {
if (res.confirm) {
uni.navigateTo({
// url: "../../address/pages/addressLists/addressLists",
});
}
},
});
}
// selectData.value = res.data.list;
initCartInfo();
}
async function editDefaultAddress() {
const res = await editDefaultAddressApi({
addrId: uni.getStorageSync("addressId"),
});
if (res.code !== "0") {
uni.showToast({
title: res.message,
duration: 3000,
});
}
}
/**
* 获取用户信息
*/
async function getUserInfo() {
const res = await getUserInfoApi();
if (res.code === "0") {
if (!res.data.company) {
uni.showModal({
title: "提示",
content: "请先添加企业",
complete: (res) => {
if (res.confirm) {
// uni.navigateTo({
// url: "../../enterprise/pages/joinEnterprise/joinEnterprise",
// });
}
},
});
}
getMyArea();
}
else if (res.code === "4") {
cartLists.value = [];
uni.showModal({
title: "提示",
content: "请先登录",
complete: (res) => {
if (res.confirm) {
uni.navigateTo({
url: "/pages/login/login",
});
}
},
});
}
}
/**
* 初始化购物车列表
*/
async function initCartInfo() {
// checkedItems.value = [];
// isLoading.value = true;
const res = await getCartInfoApi({
warehouseId: uni.getStorageSync("warehousId"),
addrId: uni.getStorageSync("addressId"),
});
if (res.code === "0") {
let checkedItems = res.data.map(each => each.id);
// isLoading.value = false;
cartLists.value = res.data.map(each => ({
...each,
checked: true,
isTouchMove: false,
}));
if (checkedItems.length) {
// 修改购物车列表
store.changeCartList(cartLists.value);
totalPrice.value = res.data.reduce((acc, cur) => acc + cur.amount, 0).toFixed(2);
await preview(checkedItems);
}
else {
checkedItems = [];
// genOrder.value = false;
serviceFee.value = 0;
shippingFee.value = 0;
totalPrice.value = 0;
}
inspectCheck();
}
else if (res.code === "4") {
uni.navigateTo({
url: "/pages/login/login",
});
}
}
/**
* 预览订单 统计价格情况
* @param itemIds 购物车项id列表
*/
async function preview(itemIds) {
if (itemIds.join(",").length === 0) {
serviceFee.value = 0;
shippingFee.value = 0;
totalPrice.value = 0;
return;
}
const res = await previewApi({
itemIds: itemIds.join(","),
addrId: uni.getStorageSync("addressId"),
});
if (res.code === "0") {
serviceFee.value = res.data.serviceFee;
shippingFee.value = res.data.shippingFee;
totalPrice.value = res.data.itemAmount;
}
// else if (res.code === "1") {
// genOrder.value = false;
// }
}
/**
* 检查是否全选
*/
function inspectCheck() {
checkedAll.value = cartLists.value.every(each => each.checked);
cartCount.value = cartLists.value.filter(each => each.checked).length;
}
onShow(() => {
finished.value = true;
// isOne.value = true;
// cartList.value = [];
// inShow.value = true;
getUserInfo();
// initCartInfo();
});
onLoad(() => {
// #ifdef APP
uni.hideTabBar();
// #endif
});
</script>
<template>
<navv>
<template #default="{ menu, status, content }">
<!-- 购物车标题 -->
<view
class="header-box"
:style="{
paddingTop: `${status}px`,
height: `${menu || 45}px`,
}"
>
<!-- <text class="iconfont icon-search1" /> -->
<text>购物车</text>
</view>
<!-- 收获地址 -->
<view id="address" class="address">
<view class="left" @tap="onGoSelectAddress">
<text class="iconfont icon-positioning" />
<text class="name">
{{ selectAddress.street }}
</text>
<text class="iconfont icon-right" />
</view>
<view class="right" @tap="onManageCart">
{{ finished ? '编辑' : '完成' }}
</view>
</view>
<!-- 列表 -->
<view
v-if="cartLists.length > 0"
class="third"
:style="{ 'height': `calc(${content}px - 14vh)`, 'overflow-y': 'auto' }"
>
<!-- 购物车列表 -->
<view class="top-box" :style="{ paddingBottom: `${store.tabbarBottomHeight}px` }">
<view
v-for="(item, index) in cartLists"
:key="item.id"
class="cart-list"
:class="[item.isTouchMove ? 'cart-list-touch-move-active' : '']"
@touchstart="(e) => onTouchStart(e)"
@touchmove="(e) => onTouchMove(e, index)"
>
<radio
:value="item.id"
:checked="item.checked"
color="#06CA64"
@tap="() => onCheckboxChange(item)"
/>
<view
class="cart-list-item"
>
<!-- style="display: flex;align-items: center;position: relative;flex: 1;" -->
<view v-if="item.type == 2 || item.type == 3" class="of-stock" />
<view v-if="item.type == 2" class="of-stock-text">
下架
</view>
<view v-if="item.type == 3" class="of-stock-text">
补货中
</view>
<view class="image-box">
<image :src="item.productImage" mode="" />
</view>
<view class="info">
<view class="name">
{{ item.productName }}
</view>
<view class="under">
<view class="price-box">
<view class="price">
¥<text>{{ item.price }}</text>
</view>
<text class="no-original">
/{{ item.unit }}
</text>
</view>
<view v-if="item.quantity !== 0" class="choose-box">
<block v-if="item.quantity !== 0">
<view class="line-one" />
<view class="line-two" />
<view class="minus" @tap.stop="(e) => onMinus(e, item, index)">
<image class="icon" src="/static/home/minus.png" mode="" />
</view>
<!-- <text
class="input"
@tap="determine"
>
{{ item.quantity }}
</text> -->
<text
class="input"
@tap="() => isShowkeyboard = true"
>
{{ item.quantity }}
</text>
<keyboard
v-show="isShowkeyboard"
@confirm="(val) => determine(val, item)"
@cancel="() => isShowkeyboard = false"
/>
</block>
<view class="add" @tap.stop="(e) => onAdd(e, item, index)">
<image class="icon" src="/static/home/add.png" mode="" />
</view>
</view>
</view>
<view class="total">
合计:<text class="text">
¥{{ item.amount }}
</text>
</view>
</view>
</view>
<view class="delete" @tap="() => onDelete(index)">
删除
</view>
</view>
</view>
<!-- 底部统计栏 -->
<view
id="under-box"
class="under-box" :style="{
bottom: `${store.tabbarBottomHeight}px`,
}"
>
<view
class="under-box-left"
@tap.stop="() => onSelectAllGoods()"
>
<radio
:checked="checkedAll"
color="#06CA64"
@tap.stop="() => onSelectAllGoods()"
/>
<view class="radio-right">
<text class="text">
全选
</text>
<view class="total-goods">
<text class="num">
{{ cartCount }}
</text>
<text class="name">
类商品
</text>
</view>
</view>
</view>
<view v-if="finished" class="under-box-right">
<view class="one">
<view class="price-box">
<text class="text">
合计:
</text>
<view class="price">
¥<text>{{ totalPrice }}</text>
</view>
</view>
<text class="tips">
超时服务费{{ serviceFee }}元,服务费{{ shippingFee }}元
</text>
</view>
<!-- :disabled="!genOrder" -->
<button class="two" @tap="onGotoOrder">
去结算({{ cartCount }})
</button>
</view>
<view v-else class="edit-box">
<view class="btn" @tap="onDeleteCart">
删除所选
</view>
</view>
</view>
</view>
<view v-if="cartLists.length == 0" class="empty-box">
<image class="icon" src="@/static/shoppingCart/pic.png" mode="widthFix" />
<text class="tips">
这里空空如也,快去添加吧~
</text>
<view class="btn" @tap="onGotoAlls">
去逛逛
</view>
</view>
<!-- 购物车小球 -->
<view
v-if="showBall"
:animation="animationY"
:style="{ position: 'fixed', top: `${testY}px` }"
>
<view
class="round"
:animation="animationX"
:style="{ position: 'fixed', left: `${testX}px` }"
/>
</view>
<!-- 底部导航栏 -->
<customTabBar tab-index="3" />
</template>
</navv>
</template>
<style lang="scss" scoped>
:deep(.left .icon-back) {
display: none;
}
@import "./shoppingCart.scss";
</style>