菜大王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.
 
 
 

888 lines
23 KiB

<script setup>
import { onLoad, onShow } from "@dcloudio/uni-app";
// import moment from "moment";
import { ref } from "vue";
import customTabBar from "@/components/custom-tab-bar/my-tab-bar.vue";
import navv from "@/components/nav/nav.vue";
import {
addCartApi,
// editDefaultAddressApi,
// getBroadcastApi,
getCarouselsApi,
getCartInfoApi,
getMyAreaApi,
getNoticesApi,
getProductsApi,
getProductTypesApi,
getProductTypesHomeApi,
getSystemSettingApi,
// myOrdersApi,
} from "@/libs/api";
import { sleep, validates } from "@/libs/utils";
import useStore from "@/store";
// import permissionPopup from "@/components/permissionPopup/permissionPopup.vue";
// import checkUpdate from "@/uni_modules/uni-upgrade-center-app/utils/check-update";
const store = useStore();
/**
* 购物车小球是否显示
*/
const showBall = ref(false);
/**
* 购物车小球是否可以点击
*/
const actionTime = ref(true);
/**
* 购物车小球的Y轴动画
*/
const animationY = ref({});
/**
* 购物车小球的X轴动画
*/
const animationX = ref({});
/**
* 购物车小球的Y轴坐标
*/
const testY = ref(0);
// 购物车小球的X轴坐标
const testX = ref(0);
/**
*商品种类
*/
const types = ref([]);
/**
* 是否显示招聘
*/
const isShowRecruitment = ref(false);
/**
* 轮播图
*/
const banner = ref([]);
/**
* 当前轮播图下标
*/
const swiperCurrent = ref(0);
/**
* 轮播图是否自动轮播
*/
const bannerAutoplay = ref(false);
/**
* 是否显示轮播图
*/
const isShowBanner = ref(false);
/**
* 选中地址
*/
const selectAddress = ref(null);
/**
* 搜索内容
*/
const keyValue = ref("");
/**
* 公告
*/
const notices = ref([]);
/**
* 特价、新品专区选中index
*/
const activeTab = ref(0);
/**
* 特价、新品专区详细
*/
const tabList = [{
title: "特价专区",
desc: "与正品同品质",
}, {
title: "新品专区",
desc: "近期上架新品",
}];
/**
* 特价商品
*/
const products = ref([]);
/**
* 新品商品
*/
const newGoods = ref([]);
/**
* 输入框
*/
const inputValue = ref("");
/**
* 是否显示键盘
*/
const isShowkeyboard = ref(false);
/**
* 招聘求职有关信息查询
*/
async function getIosShow() {
const params = { black: "XCXenterCDW", key: "config.enterCDW" };
const res = await getSystemSettingApi(params);
// 返回 0 不显示,其他反之
isShowRecruitment.value = res.code !== "0";
// if (res.data.code === 0) {
// isShowRecruitment.value = false;
// menus.value = menus.value.filter(item => item.name !== "走进菜大王" || item.name !== "招聘求职");
// }
// else {
// isShowRecruitment.value = true;
// }
}
/**
* 获取轮播图
*/
async function getBanner() {
banner.value = [];
const res = await getCarouselsApi();
swiperCurrent.value = 0;
bannerAutoplay.value = true;
banner.value = res.data;
isShowBanner.value = true;
}
/**
* 获取产品分类
*/
async function getProductCategory() {
const res = await getProductTypesHomeApi();
types.value = res.data;
}
/**
* 获取公告列表
*/
async function getNoticesApiList() {
const res = await getNoticesApi();
notices.value = res.data;
}
/**
* 获取特价新品商品种类
*/
async function getProductTypes() {
const res = await getProductTypesApi();
if (res.code !== "0") {
return;
}
const eachsId = res.data.reduce((acc, cur) => {
if (cur.name === "新品" || cur.name === "特价专区") {
acc.push(cur);
}
return acc;
}, []);
for await (const each of eachsId) {
// 不是新品和特价专区的都跳过
if (!(each.name === "新品" || each.name === "特价专区")) {
return;
}
const res = await getProductTypesApi({ parentId: each.id });
if (res.code !== "0")
return;
const typeId = res.data[0].id;
const params = {
typeId,
promotion: false,
orderByField: "name",
[each.name === "特价专区" ? "asc" : "ase"]: true,
search: "",
pageNum: 1,
pageSize: 5,
warehouseid: uni.getStorageSync("warehousId"),
};
const res2 = await getProductsApi(params);
if (res2.code !== "0")
return;
// 初始化状态
res2.data.forEach((item) => {
// 判断是否显示“选规格”按钮
item.showChoose = (item.specs.length > 1);
// 从映射表中获取数量(不存在则设为0)
item.specs.forEach((specsItem) => {
specsItem.sum = 0;
});
});
// 初始化购物车映射表(格式:specId_price -> quantity)
const cartMap = store.getCartMap();
res2.data.forEach((item) => {
item.specs.forEach((specsItem) => {
// 为每个规格项生成相同的组合键
const specKey = `${specsItem.id}_${specsItem.price}`;
// 从映射表中获取数量(不存在则设为0)
specsItem.sum = cartMap[specKey] || 0;
});
// 判断是否显示“选规格”按钮
item.showChoose = item.specs.length > 1 ? 1 : 0;
item.showChild = item.specs.length > 1 ? false : item.showChild;
});
if (each.name === "新品") {
newGoods.value = res2.data;
}
else {
products.value = res2.data;
}
}
}
function getDFKDataLength() {
// const nowDay = moment().format("YYYY-MM-DD");
// myOrdersApi({
// orderNo: "",
// status: "DFK",
// pageNum: 1,
// pageSize: 10,
// addrId: "",
// });
}
/**
* 获取仓库列表数据
* 随后获取特价新品
*/
async function getMyArea() {
uni.showLoading({
title: "加载中",
mask: true,
});
const res = await getMyAreaApi({
warehouseid: "",
isEnabled: 1,
});
if (res.code !== "0") {
uni.hideLoading();
return;
}
const list = res.data.list.sort((pre, cur) => Number(cur) - Number(pre));
// each 就是找到有isLastDefaultAddr,没有就那第一个值
const each = list.find(each => each.isLastDefaultAddr) || list[0];
uni.setStorageSync("warehousId", each.warehousId);
uni.setStorageSync("addressId", each.addrId);
selectAddress.value = list[0];
// getBroadcast();
getDFKDataLength();
if (!res.data.login) {
// FIXME: hiddenLogin.value = false;
}
else {
// TODO: getDFKDataLength
// getDFKDataLength();
}
await getProductTypes();
uni.hideLoading();
}
/**
* 获取广播
*/
// async function getBroadcast() {
// const res = await getBroadcastApi({
// warehouseId: uni.getStorageSync("warehousId"),
// });
// if (res.code !== "0") {
// return;
// }
// const item = res.data.findLast(item => item.status == 1);
// if (JSON.stringify(res.data) != "{}") {
// const nowTime = moment().format("YYYY-MM-DD HH:mm:ss");
// }
// // TODO: 后续修改
// // broadcastText.value = item;
// }
/**
* 查看公告列表
*/
async function onNotice() {
uni.navigateTo({
url: "/pages/home/announcement",
});
}
/**
* 切换特价、新品事件
*/
function onChangeTab(index) {
activeTab.value = index;
}
/**
* 轮播图切换
*/
function changeSwiper(e) {
const index = e.detail.current;
swiperCurrent.value = index;
}
/**
* 选择地址
*/
function onGotoSelectAddress() {
uni.navigateTo({
url: "/pages/deliveryAddress/deliveryAddress",
});
}
/**
* 分享微信
*/
function shareWX() { }
/**
* 点击更多商品规格
*/
function onMore(item) {
store.productTypeId = item.id; // 点击更多商品规格时,将商品种类id存储到store中
uni.switchTab({
url: `/pages/allDish/allDish?id=${item.id}`,
});
// uni.showToast({
// title: "待完善",
// icon: "none",
// });
}
/**
* 打开附近企业
*/
function onFriends() {
uni.showToast({
title: "待完善",
icon: "none",
});
}
/**
* 打开廉洁协议
*/
function onHonest() {
uni.navigateTo({
url: "/pages/home/honest",
});
}
/**
* 打开走进菜大王
*/
function onEnterPage() {
uni.navigateTo({
url: "/pages/home/enterKing/enterKing",
});
// enterVegetableKing
// uni.showToast({
// title: "待完善",
// icon: "none",
// });
}
/**
* 打开招聘求职
*/
function onJoin() {
uni.showToast({
title: "敬请期待,业务正在开发中",
icon: "none",
});
}
/**
* -1 到购物车
* @param {{specs: {sum: number}[]}} item
*/
function onMinusGoods(item) {
const isPass = validates([
() => item.stock == item.sum && "采购数量不能大于库存数量",
() => item.sum === 0 && "库存数量为0",
() => item.stock === 0 && "库存数量为0无法添加",
]);
if (!isPass) {
return;
}
// 判空为 0, 否则转换为数字
item.sum = Number(item.sum) || 0;
const minNum = item.minNum || 1;
// 达到起订量(item.minNum)直接清空, 否则每次-1
const diff = -(item.sum === minNum ? minNum : 1);
item.sum += diff;
// 保持输入框与购物车数量同步
inputValue.value = item.sum;
toCart(item, diff);
}
/**
* +1 到购物车
* @param {{specs: {sum: number}[]}} item
*/
function onAddGoods(item, e) {
// const cur = item.specs[index];
if (!actionTime.value) {
return;
}
createAnimation(e.detail.x, e.detail.y);
const isPass = validates([
() => item.stock == item.sum && "采购数量不能大于库存数量",
() => item.stock == 0 && "库存数量为0无法添加",
]);
if (!isPass) {
return;
}
// 判空为 0, 否则转换为数字
item.sum = Number(item.sum) || 0;
// 起订量(item.minNum), 否则每次+1
const diff = item.sum === 0 ? (item.minNum || 1) : 1;
item.sum += diff;
// 保持输入框与购物车数量同步
inputValue.value = item.sum;
toCart(item, diff);
}
/**
* 更新购物车
* @param item 商品项
* @param diff 变化量
*/
async function toCart(item, diff) {
const data = {
quantity: diff,
specId: item.id,
Chuxiao: item.chuxiao,
isChuxiao: false,
warehouseId: uni.getStorageSync("warehousId"),
addrId: uni.getStorageSync("addressId"),
// isChuxiao: item.chuxiao,
};
if (!data.specId) {
uni.showModal({
title: "提示",
content: "当前商品规格错误,请稍候再试",
showCancel: false,
confirmText: "确定",
});
}
const res = addCartApi(data);
if (res.code !== "0") {
initCartInfo();
}
}
/**
* 初始化购物车信息
*/
async function initCartInfo() {
const data = {
warehouseId: uni.getStorageSync("warehousId"),
addrId: uni.getStorageSync("addressId"),
};
const res = await getCartInfoApi(data);
if (res.code !== "0")
return;
store.changeCartList(res.data);
}
/**
*
* @param {InputEvent} e
* @param {{specs: {sum: number}[]}} item
*/
function onChangeGoods(e, item, index) {
const value = e.detail.value * 1;
if (Number.isNaN(value))
return;
if (value >= item.specs[index].stock) {
uni.showToast({
icon: "none",
title: "采购数量不能大于库存数量",
});
// item.specs[0].sum = item.specs[0].stock;
return;
}
item.specs[index].sum = value;
}
/**
* 创建购物车小球的动画
*/
async function createAnimation(eX, eY) {
actionTime.value = false;
testX.value = eX;
testY.value = 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;
}
onLoad(() => {
// #ifdef APP
uni.hideTabBar();
// #endif
getIosShow();
store.fontScale = uni.getStorageSync("fontScale") || 1;
// uni.hideTabBar();
getBanner(); // 获取轮播图
getProductCategory(); // 获取商品分类
getNoticesApiList(); // 获取公告
});
onShow(async () => {
await sleep(1000);
await initCartInfo();
getMyArea(); // 获取仓库列表数据
});
</script>
<template>
<navv>
<template #default="{ status, menu }">
<view
class="main"
:style="{
paddingBottom: status ? `${status + 45}px` : '89px',
}"
>
<view
class="header-box"
:style="{
background:
`linear-gradient(180deg,
${banner[swiperCurrent]?.rgbStart ?? '#06CA64'},
${banner[swiperCurrent]?.rgbEnd ?? '#06CA64'})`,
}"
>
<!-- 搜索框和定位分享 -->
<view
class="location-share"
:style="{
paddingTop: `${status}px`,
}"
>
<!-- #ifdef APP-PLUS|H5 -->
<view :style="{ width: '100vw', height: '30rpx' }" />
<!-- #endif -->
<view
class="location-box"
:style="{ height: menu ? `${menu}px` : 'auto' }"
>
<!-- left -->
<view class="left" @tap="onGotoSelectAddress">
<text class="iconfont icon-positioning" />
<text class="address">
{{ selectAddress?.street ?? '获取中...' }}
</text>
</view>
<!-- right -->
<!-- #ifdef APP-PLUS -->
<view class="" @tap="shareWX">
<!-- TODO: shareWX todo -->
<text class="iconfont icon-share" />
</view>
<!-- #endif -->
</view>
<!-- 搜索框 -->
<view class="search-input">
<text class="iconfont icon-search1 " />
<!-- <uv-icon name="search" size="38rpx" color="white" /> -->
<input
v-model="keyValue"
class="input"
shape="circle"
color="white"
>
<text class="iconfont icon-yuyin" />
</view>
</view>
<!-- 轮播图 -->
<view class="carousel-box">
<swiper
v-if="banner.length !== 0 && isShowBanner"
:indicator-dots="false"
:autoplay="bannerAutoplay"
:current="swiperCurrent"
:interval="3000"
previous-margin="66rpx"
next-margin="66rpx"
circular
class="h-full w-full"
@change="changeSwiper"
>
<swiper-item v-for="(item, index) in banner" :key="index" class="swiper-item">
<image
:style="{ transform: `scale(${swiperCurrent === index ? 1 : 0.9})` }"
class="image"
:src="item.url"
mode="widthFix"
/>
</swiper-item>
</swiper>
</view>
</view>
<!-- 金刚区 -->
<view class="food-type-box">
<template v-for="(item, index) in types" :key="index">
<view v-if="item.name !== '新品'" class="type-list" @tap="() => onMore(item)">
<image class="icon" :src="item.imageUrl" mode="scaleToFill" />
<text class="name">
{{ item.name }}
</text>
</view>
</template>
</view>
<!-- 公告区 -->
<view class="newNotice-box">
<image class="bg" mode="" src="/static/home/notice_bg.png" />
<image class="icon" mode="" src="/static/home/notice_icon.png" />
<swiper class="swiper" autoplay interval="2000" vertical @tap="onNotice">
<swiper-item v-for="(item, index) in notices" :key="index" class="swiper-item">
<text class="text">
{{ item }}
</text>
</swiper-item>
</swiper>
</view>
<!-- 功能区 -->
<view class="functional-box">
<!-- TODO: onFriends -->
<view class="left" @tap="onFriends">
<image class="img" src="/static/home/functional1.png" />
<view class="text-box">
<text class="first">
谁在用菜大王
</text>
<text class="second">
查看附近企业
</text>
<view class="line" />
</view>
</view>
<view class="right">
<view class="top" @tap="onHonest">
<image class="img" src="/static/home/functional2.png" />
<view class="text-box">
<text class="first">
廉洁协议
</text>
<text class="second">
价格透明 拒绝回扣
</text>
<view class="line" />
</view>
</view>
<view class="under">
<view class="one" @tap="onEnterPage">
<image class="img" src="/static/home/functional3.png" />
<view class="text-box">
<text class="first">
走进菜大王
</text>
<view class="line" />
</view>
</view>
<!-- TODO: onJoin -->
<view class="two" @tap="onJoin">
<image class="img" src="/static/home/functional4.png" />
<view class="text-box">
<text v-if="isShowRecruitment" class="first">
招聘求职
</text>
<view v-if="isShowRecruitment" class="line" />
</view>
</view>
</view>
</view>
</view>
<!-- 特价、新品专区 -->
<view class="product-nav-box">
<view
v-for="(item, index) in tabList"
:key="index"
class="item"
@tap="() => onChangeTab(index)"
>
<view v-show="index === activeTab" class="rotundity" />
<text class="title" :class="[index === activeTab ? 'title-active' : '']">
{{ item.title }}
</text>
<text class="desc" :class="[index === activeTab ? 'desc-active' : '']">
{{ item.desc }}
</text>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-list-box">
<view
v-for="(item, index) in (activeTab === 0 ? products : newGoods)"
:key="index"
class="item"
>
<view class="goods-top">
<view class="left">
<image class="img" :src="item.imageUrl" />
</view>
<view class="right">
<view class="name">
{{ item.name }}
</view>
<view class="inventory">
{{ item.specs[0].stock === -1 ? '' : `库存:${item.specs[0].stock}` }}
</view>
<view class="info">
<view class="price-box">
<view class="price">
<text>¥</text>{{ item.specs[0].price }}
</view>
<text v-if="item.specs[0].chuxiao" class="original">
正价:{{ item.specs[0].oldPrice }}/{{ item.specs[0].unit }}
</text>
<text v-else class="no-original">
/{{ item.specs[0].unit }}
</text>
</view>
<view v-if="!item.showChoose" class="choose-box">
<block v-if="item.specs[0].sum !== 0">
<view class="line-one" />
<view class="line-two" />
<view class="minus" @tap="() => onMinusGoods(item.specs[0], index)">
<image class="icon" src="/static/home/minus.png" mode="" />
</view>
<text
class="input"
@tap="() => showKeyboard(item.specs[0], index, childsIndex, tabActive)"
>
{{ item.specs[0].sum }}
</text>
<!-- <input
:value="item.specs[0].sum"
class="input"
type="number"
@input="e => onChangeGoods(e, item)"
> -->
</block>
<view class="add" @tap="(e) => onAddGoods(item.specs[0], e)">
<image class="icon" src="/static/home/add.png" mode="" />
</view>
</view>
<view
v-if="item.showChoose"
class="specifications"
@tap="() => item.showChild = !item.showChild"
>
{{ item.showChild ? '收起' : '选规格' }}
</view>
</view>
</view>
</view>
<view v-if="item.showChild" class="goods-under">
<view
v-for="(specsItem, specsIndex) in item.specs"
:key="specsIndex"
class="goods-under-list"
>
<view class="price-box">
<view class="price">
<text>¥</text>{{ specsItem.price }}
</view>
<text v-if="item.specs[0].chuxiao" class="original">
/{{ specsItem.unit }}
</text>
<text v-else class="no-original">
/{{ item.specs[0].unit }}
</text>
</view>
<view class="choose-box">
<block v-if="specsItem.sum !== 0 || specsItem.sum !== ''">
<view class="line-one" />
<view class="line-two" />
<view class="minus" @tap="() => onMinusGoods(item.specs[specsIndex])">
<image class="icon" src="/static/home/minus.png" mode="" />
</view>
<input
:value="specsItem.sum"
class="input"
type="number"
@input="e => onChangeGoods(e, item, specsItem)"
>
</block>
<view class="add" @tap="(e) => onAddGoods(item.specs[specsIndex], e)">
<image class="icon" src="/static/home/add.png" mode="" />
</view>
</view>
</view>
</view>
</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="0" />
</view>
</template>
</navv>
</template>
<style lang="scss" scoped>
@import './home.scss'
</style>