This commit is contained in:
2024-09-10 10:49:08 +08:00
parent b5fd06b800
commit dd4f5938da
6391 changed files with 722800 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
<template>
<view class="mask" @tap="hideGoods" v-if="switchGoods"></view>
<view class="car border-top u-flex u-row-between u-col-bottom u-relative">
<view class="u-absolute goods bg-fff">
<view
class="u-p-t-32 color-666 border-bottom bg-fff u-absolute total u-p-r-28 u-p-b-32 u-p-l-28 u-flex u-row-between">
<view>已添加{{goodsNumber}}件商品</view>
<view class="color-666">
<uni-icons color="#666" type="trash"></uni-icons>
<text class="u-m-l-10" @tap="clear">清空</text>
</view>
</view>
<scroll-view scroll-y="true" class="tranistion" :style="{height:switchGoods?'50vh':0 }">
<!-- 占位 -->
<view class="u-p-t-32 color-666 border-bottom u-p-r-28 u-p-b-32 u-p-l-28 u-flex u-row-between"
style="opacity: 0;">
<view>已添加{{goodsNumber}}件商品</view>
<view class="color-666">
<uni-icons color="#666" type="trash"></uni-icons>
<text class="u-m-l-10">清空</text>
</view>
</view>
<!-- 占位 -->
<view class="color-333 item border-top u-flex u-row-center u-row-between" v-for="(item,index) in data"
:key="index">
<view class="">
<view>{{item.name}}</view>
<view class="u-m-t-10 u-font-24 color-666">{{item.specSnap||''}}</view>
</view>
<view class="u-flex">
<view class="font-bold red u-m-r-32">{{formatPrice(item.salePrice*item.number) }}</view>
<view class="u-flex" @tap="updateNumber(false,index,item)">
<image src="/pagesCreateOrder/static/images/icon-reduce-black.svg" class="icon" mode="">
</image>
</view>
<view class="u-m-l-30 u-m-r-30 color-333">
{{item.number}}
</view>
<view class="u-flex" @tap="updateNumber(true,index,item)">
<image src="/pagesCreateOrder/static/images/icon-add-black.svg" class="icon" mode="">
</image>
</view>
</view>
</view>
<my-empty v-if="!data.length" text="暂未有添加商品"></my-empty>
</scroll-view>
</view>
<view class="icon-car-box" @tap="toggleGoods">
<image src="/pagesCreateOrder/static/images/icon-car.svg" class="icon-car" />
<view class="dot">{{goodsNumber}}</view>
</view>
<view class="price font-bold u-flex">
<view></view>
<view>{{allPrice}}</view>
</view>
<my-button shape="circle" height="80" width="220" @tap="toConfimOrder">去下单</my-button>
</view>
</template>
<script setup>
import go from '@/commons/utils/go.js';
import infoBox from '@/commons/utils/infoBox.js';
import {formatPrice} from '@/commons/utils/format.js';
import {
computed,
ref
} from 'vue';
const props = defineProps({
data: {
type: Array,
default: () => {
return []
}
},
user:{
type: Object,
default: () => {
return {
id: ""
}
}
},
table:{
type: Object,
default: () => {
return {
tableId:''
}
}
},
masterId:{
type: [String,Number],
default:''
}
})
const edmits = defineEmits(['clear', 'updateNumber'])
// mask
let switchGoods = ref(false)
function hideGoods() {
switchGoods.value = false
}
function showGoods() {
switchGoods.value = true
}
function toggleGoods() {
switchGoods.value = !switchGoods.value
}
function toConfimOrder() {
console.log(props.user);
if(props.data.length<=0){
return infoBox.showToast('还没有选择商品')
}
go.to('PAGES_CONFIRM_ORDER',{
...props.user,
masterId:props.masterId,
...props.table,
})
}
const allPrice = computed(() => {
return props.data.reduce((prve,cur)=>{
return prve+cur.salePrice*cur.number
},0).toFixed(2)
})
const goodsNumber = computed(() => {
let result = 0
result = props.data.reduce((prve, cur) => {
return prve + cur.number
}, 0)
return result >= 99 ? 99 : result
})
function updateNumber(isAdd, index, goods) {
const step = isAdd ? 1 : -1
const newval = goods.number + step
const par = {
num: newval,
index: index,
goods: goods
}
edmits('updateNumber', par)
}
function clear() {
uni.showModal({
title: '提示',
content: '是否清空全部已添加的商品?',
success(res) {
if (res.confirm) {
edmits('clear')
hideGoods()
}
}
})
}
import myButton from '@/components/my-components/my-button.vue'
</script>
<style lang="scss" scoped>
$car-size: 96rpx;
$car-top: -16rpx;
@mixin fixedAll {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.total {
left: 0;
right: 0;
top: 0;
z-index: 1;
}
.icon {
width: 40rpx;
height: 40rpx
}
.mask {
@include fixedAll;
background: rgba(51, 51, 51, 0.5);
}
.goods {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
transition: all .2s ease-in-out;
overflow: hidden;
.item {
padding: 32rpx 28rpx;
}
}
.border-bottom {
border-bottom: 1px solid #E5E5E5;
}
.border-top {
border-top: 1px solid #E5E5E5;
}
.red {
color: #EB4F4F;
}
.car {
padding: 0 28rpx;
position: relative;
left: 0;
right: 0;
background-color: #fff;
padding-top: 10rpx;
bottom: 0;
padding-bottom: calc(1px + env(safe-area-inset-bottom));
/* #ifdef H5 */
padding-bottom: 68rpx;
/* #endif */
.icon-car-box {
position: absolute;
left: 28rpx;
top: $car-top;
display: flex;
width: $car-size;
height: $car-size;
justify-content: center;
align-items: center;
z-index: 2;
.dot {
position: absolute;
right: 0;
top: 0;
width: 28rpx;
height: 28rpx;
background: #EB4F4F;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 20rpx;
font-weight: bold;
color: #FFFFFF;
}
}
.price {
color: #EB4F4F;
margin-left: calc(38rpx + $car-size);
transform: translateY(calc($car-top / 2));
}
.icon-car {
width: $car-size;
height: $car-size;
}
}
</style>

View File

@@ -0,0 +1,140 @@
<template>
<view class="u-relative u-flex item">
<image lazy-load class="img" :src="data.coverImg" mode=""></image>
<view class="info u-flex u-row-between u-col-top u-flex-col" @tap="emitEvent('add')">
<view>
<view>{{data.name}}</view>
<view class="u-font-32 font-bold u-m-t-16">
{{data.price}}
</view>
</view>
<view class="u-flex">
<template v-if="!isSellout">
<template v-if="!data.isDan">
<button class="btn" hover-class="btn-hover-class" @tap="emitEvent('chooseGuige')">选规格</button>
</template>
<template v-else>
<view class="u-flex icon-btn">
<view class="u-flex" @tap.stop="emitEvent('add')">
<image src="/pagesCreateOrder/static/images/icon-add.svg" class="icon" mode=""></image>
</view>
<template v-if="data.chooseNumber">
<view class="u-font-32">
{{data.chooseNumber}}
</view>
<view class="u-flex" @tap.stop="emitEvent('reduce')">
<image src="/pagesCreateOrder/static/images/icon-reduce.svg" class="icon" mode="">
</image>
</view>
</template>
</view>
</template>
</template>
<template v-else>
<view class=" u-m-t-16">
已售罄
</view>
</template>
</view>
</view>
</view>
</template>
<script setup>
import {
computed,
toRef,
toRefs,
watch
} from 'vue';
const props = defineProps({
index: {
type: Number,
},
data: {
type: Object,
default: () => {
return {
chooseNumber: 0
}
}
}
})
//判断是否是菜品
function isGoods(){
return props.data.hasOwnProperty('id')
}
//判断商品是否售尽
const isSellout = computed(() => {
const item = props.data
if(!isGoods()){
return false
}
return (
item.isPauseSale ||
(item.typeEnum !== "sku" && item.specList[0].stockNumber <= 0)
);
})
const emits = defineEmits(['add', 'reduce', 'chooseGuige'])
function emitEvent(emitName){
if(isGoods()){
emits(emitName, props.index)
}
}
</script>
<style lang="scss" scoped>
.icon {
width: 48rpx;
height: 48rpx;
}
.icon-btn {
gap: 14rpx;
}
.btn {
background: #EB4F4F;
border-radius: 100rpx;
font-size: 28rpx;
height: 56rpx;
line-height: 56rpx;
color: #fff;
}
.btn-hover-class {
opacity: .6;
}
.item {
width: 250rpx;
height: 272rpx;
background: #F9B798;
border-radius: 8rpx 8rpx 8rpx 8rpx;
overflow: hidden;
.img {
width: 250rpx;
height: 272rpx;
}
.info {
position: absolute;
left: 0;
right: 0;
bottom: 0;
color: #fff;
padding: 32rpx 24rpx 24rpx 24rpx;
top: 0;
background: rgba(37, 22, 15, 0.5);
border-radius: 8rpx 8rpx 8rpx 8rpx;
overflow: hidden;
}
}
</style>

View File

@@ -0,0 +1,211 @@
<template>
<my-model ref="model" borderRadius="12" :title="title">
<template #desc>
<scroll-view scroll-y="true" style="height: 50vh;" class="u-p-30 guigeModel">
<view class="u-m-b-40" v-for="(item,index) in skus" :key="index">
<view class="u-text-left">
<view class="color-333">{{item.name}}</view>
</view>
<view class="u-flex u-m-t-20 u-flex-wrap">
<view class="item" @tap="chooseSkd(index,skd)"
:class="{active:item.sel===skd.name,disabled:skd.disabled}" v-for="(skd,skdIndex) in item.values"
:key="skdIndex">
{{skd.name}}
</view>
</view>
</view>
</scroll-view>
</template>
<template #btn>
<view class="u-p-30 border-top ">
<view class="u-flex u-p-b-30 u-row-between">
<view class="price" >
<template v-if="goods&&goods.isGrounding">
<text></text>
<text>{{to2(goods.salePrice*number) }}</text>
</template>
</view>
<view class="u-flex">
<view class="u-flex" @tap="reduce">
<image src="/pagesCreateOrder/static/images/icon-reduce-black.svg" class="icon" mode="">
</image>
</view>
<view class="u-m-l-30 u-m-r-30 color-333">
{{number}}
</view>
<view class="u-flex" @tap="add">
<image src="/pagesCreateOrder/static/images/icon-add-black.svg" class="icon" mode="">
</image>
</view>
</view>
</view>
<view class="u-m-t-10">
<my-button @tap="close" type="cancel" v-if="isAllDisabled||!goods.isGrounding"><view class="color-999">已下架</view></my-button>
<my-button @tap="confirm" v-else>添加</my-button>
</view>
</view>
</template>
</my-model>
</template>
<script setup>
import {
computed,
reactive,
ref
} from 'vue';
import myModel from '@/components/my-components/my-model.vue'
import myButton from '@/components/my-components/my-button.vue'
const props = defineProps({
title: {
type: String,
default: ''
},
skuMap: {
type: Object,
default: () => {
return {}
}
},
skus: {
type: Array,
default: () => {
return []
}
},
defaultIndex: {
type: Array,
default: () => {
return []
}
}
})
function to2(number){
return Number(number).toFixed(2)
}
const selSku=computed(()=>{
return props.skus.reduce((prve,cur)=>{
prve.push(cur.sel)
return prve
},[]).join()
})
const goods=computed(()=>{
return props.skuMap[selSku.value]
})
//全部规格是否都无法使用
const isAllDisabled=computed(()=>{
console.log(props.skus);
return props.skus.reduce((prve,cur)=>{
return prve&&cur.values.filter(v=>v.disabled).length===cur.values.length
},true)
})
const emits = defineEmits(['confirm','updateSku'])
let number = ref(1)
function chooseSkd(skusIndex, skd) {
const {name,disabled}=skd
if(disabled){
return
}
if(props.skus[skusIndex].sel!=name){
emits('updateSku',skusIndex,name)
}
}
const defaultIndex=reactive(new Array(props.skus.length).fill(''))
for(let i in props.defaultIndex){
defaultIndex[i]=props.defaultIndex[i]
}
const activeArr = defaultIndex
console.log(activeArr);
const model = ref(null)
function open() {
model.value.open()
}
function close() {
model.value.close()
}
function reduce() {
if(isDisabled()){
return
}
const newval = number.value - 1
number.value = newval <= 1 ? 1 : newval
}
function add() {
if(isDisabled()){
return
}
const newval = number.value + 1
number.value = newval
}
function isDisabled(){
return isAllDisabled.value
}
function confirm() {
close()
if(isDisabled()){
return
}
emits('confirm',goods.value,number.value)
}
defineExpose({
open,
close
})
</script>
<style lang="scss">
.border-top {}
.icon {
width: 40rpx;
height: 40rpx;
}
.guigeModel {
.item {
color: #666;
font-size: 24rpx;
padding: 4rpx 28rpx;
border: 1px solid #E5E5E5;
border-radius: 8rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
transition: all .2s ease-in-out;
&.active {
border-color: $my-main-color;
color: $my-main-color;
}
&.disabled{
color: #ccc;
border-color: #eee;
}
}
}
.price {
color: #EB4F4F;
}
.border-top {
border-top: 1px solid #E5E5E5;
}
</style>

View File

@@ -0,0 +1,109 @@
<template>
<my-model ref="model" :title="title" iconColor="#000" @close="resetForm">
<template #desc>
<view class="u-text-left u-p-30 ">
<view>
<view>名称</view>
<view class="u-m-t-24 border u-flex input-box">
<input v-model="form.name" type="text" placeholder-class="placeholder-class"
placeholder="请输入名称" />
</view>
</view>
<view class="u-m-t-32">
<view>价格</view>
<view class="u-m-t-24 border u-flex input-box">
<input v-model="form.price" type="digit" placeholder-class="placeholder-class"
placeholder="请输入价格" />
</view>
</view>
</view>
</template>
<template #btn>
<view class="u-p-30">
<view class="u-m-t-10">
<my-button @tap="confirm" shape="circle" showShadow>添加</my-button>
<my-button type="cancel" bgColor="#fff" @tap="confirm">取消</my-button>
</view>
</view>
</template>
</my-model>
</template>
<script setup>
import {
reactive,
ref
} from 'vue';
import myModel from '@/components/my-components/my-model.vue'
import myButton from '@/components/my-components/my-button.vue'
const props = defineProps({
title: {
type: String,
default: ''
},
data: {
type: Array,
default: []
}
})
const $form = {
name: '',
price: ''
}
const form = reactive({...$form})
function resetForm() {
Object.assign(form,{...$form})
}
const model = ref(null)
function open() {
model.value.open()
}
function close() {
model.value.close()
}
const emits = defineEmits(['confirm'])
function confirm() {
const {
name,
price
} = form
if (!name) {
return uni.showToast({
icon: 'none',
title: '请输入附加费名称'
})
}
close()
emits('confirm',{
name,
price
})
}
defineExpose({
open,
close
})
</script>
<style lang="scss">
.border {
border: 1px solid #E5E5E5;
border-radius: 4rpx;
}
.input-box {
padding: 22rpx 32rpx;
font-size: 28rpx;
color: #666;
}
.placeholder-class {
font-size: 28rpx;
}
</style>

View File

@@ -0,0 +1,618 @@
<template>
<view class="u-wrap">
<view class="top bg-fff w-full">
<view class="u-flex u-row-between choose-user" @tap="chooseUser">
<view>
<view v-if="!user">选择用户</view>
<view class="u-flex" v-else>
<image class="headeimg" src="@/static/uni.png" mode=""></image>
<view class="u-m-l-20">{{user.name}}</view>
<view class="color-main u-m-l-10 u-font-24">{{user.isVip?'永久会员':'' }}</view>
</view>
</view>
<view class="u-flex">
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="search u-flex u-col-center ">
<view class="u-flex-1">
<uni-search-bar bgColor="#F9F9F9" cancelButton="none" placeholder="搜索店内商品" @confirm="search"
:focus="true" v-model="searchValue">
</uni-search-bar>
</view>
<view class="u-flex">
<image src="/pagesCreateOrder/static/images/icon-saoma.svg" class="icon-saoma" mode=""></image>
</view>
</view>
</view>
<view class="u-menu-wrap">
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="data.scrollTop"
:scroll-into-view="data.itemId">
<view v-for="(item,index) in data.tabbar" :key="index" class="u-tab-item"
:class="[data.current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{item.name}}</text>
</view>
</scroll-view>
<scroll-view :scroll-top="data.scrollRightTop" scroll-y scroll-with-animation class="right-box"
@scroll="rightScroll">
<view class="page-view u-p-l-24">
<view class="lingshi" @tap="toLinshi">
<uni-icons type="plus-filled" size="24" :color="color.ColorMain"></uni-icons>
<view class="u-m-t-24 color-main">临时菜</view>
</view>
<view class="class-item" :id="'item' + index" v-for="(item , index) in data.tabbar" :key="index">
<view class="item-title">
<text>{{item.name}}</text>
</view>
<view class="item-container">
<view class="thumb-box" v-for="(goodsItem, goodsIndex) in item.foods" :key="goodsIndex">
<!-- <image class="item-menu-image" :src="item1.icon" mode=""></image>
<view class="item-menu-name">{{item1.name}}</view> -->
<goods-item @chooseGuige="chooseGuige($event,index)" @add="goodsAdd($event,index)"
@reduce="goodsReduce($event,index)" :index="goodsIndex"
:data="goodsItem"></goods-item>
</view>
<template v-if="item.name==='附加费'">
<view class="addCai" @tap="surchargeShow">
<uni-icons type="plus-filled" size="24" :color="color.ColorMain"></uni-icons>
<view class="u-m-t-24 color-main">自定义添加</view>
</view>
</template>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="bottom w-full">
<my-car :data="cars"></my-car>
</view>
</view>
<!-- 选择规格 -->
<guige-model @confirm="guigeConfirm" ref="chooseGuigeModel" :title="guigeModelData.title"
:data="guigeModelData.chooseGoods.skus"></guige-model>
<!-- 添加附加费 -->
<my-surcharge @confirm="surchargeConfirm" ref="surcharge" title="添加附加费"></my-surcharge>
</template>
<script setup>
import * as Api from '@/http/yskApi/Instead.js'
import {
$tbShopCategory
} from '@/http/yskApi/goods.js'
import classifyData from './classify.data.js';
import color from '@/commons/color.js';
import guigeModel from './components/guige'
import goodsItem from './components/goods-item'
import mySurcharge from './components/surcharge'
import {
onLoad,
onReady,
onShow,
onPageScroll,
onPullDownRefresh
} from '@dcloudio/uni-app';
import {
onBeforeUnmount,
computed,
reactive,
ref,
nextTick
} from 'vue';
import myCar from './components/car'
import go from '@/commons/utils/go.js';
async function init() {
const {
content
} = await $tbShopCategory({
page: 0,
size: 300
})
const category = content.reduce((prve, cur) => {
prve.push({
...cur,
childrenList: null
});
return [...prve, ...cur.childrenList];
}, []);
console.log(category);
const {records}= await Api.getGoodsLists({
page: 0,
size: 300
})
const goods=records
console.log(goods);
}
// 监听选择用户事件
let user = ref(null)
function watchChooseuser() {
uni.$off('choose-user')
uni.$on('choose-user', (data) => {
user.value = data
console.log(user.value);
})
}
watchChooseuser()
onBeforeUnmount(() => {
})
const surcharge = ref(null)
function surchargeConfirm(e) {
data.tabbar[data.tabbar.length - 1].foods.unshift({
...e,
chooseNumber: 0
})
}
function surchargeShow() {
surcharge.value.open()
}
let searchValue = ref('')
function search() {
}
function chooseUser() {
go.to('PAGES_CHOOSE_USER')
}
function toLinshi() {
go.to('PAGES_ADD_TEMP_CUISINE')
}
const chooseGuigeModel = ref(null)
const guigeModelData = reactive({
title: '',
chooseGoods: {
item: '',
skus: [{
title: '口味',
skds: [{
title: '麻辣',
id: 1
},
{
title: '中辣',
id: 2
},
{
title: '微辣',
id: 3
},
{
title: '不辣',
id: 4
},
{
title: '三鲜',
id: 5
}
]
},
{
title: '辣度',
skds: [{
title: '麻辣',
id: 1
},
{
title: '中辣',
id: 2
},
{
title: '微辣',
id: 3
},
{
title: '不辣',
id: 4
},
{
title: '三鲜',
id: 5
}
]
},
{
title: '配料',
skds: [{
title: '丸子',
id: 1
},
{
title: '五花肉',
id: 2
},
{
title: '鸡块',
id: 3
},
{
title: '排骨',
id: 4
},
{
title: '火腿',
id: 5
}
]
}
]
}
})
function chooseGuige(foodsindex, index) {
const $goods = data.tabbar[index].foods[foodsindex]
guigeModelData.title = $goods.name
chooseGuigeModel.value.open()
}
function guigeConfirm(e) {
console.log(e);
}
function goodsAdd(foodsindex, index) {
const $goods = data.tabbar[index].foods[foodsindex]
const newval = $goods.chooseNumber + 1
$goods.chooseNumber = newval
let item = cars.find(v => v.id == $goods.id)
if (item) {
item.number += 1
} else {
cars.push({
...$goods,
number: 1
})
}
}
function goodsReduce(foodsindex, index) {
const $goods = data.tabbar[index].foods[foodsindex]
const newval = $goods.chooseNumber - 1
$goods.chooseNumber = newval <= 0 ? 0 : newval
}
const cars = reactive([])
const tabbar = classifyData.map(v => {
return {
...v,
foods: v.foods.map((goods, index) => {
return {
...goods,
chooseNumber: 0,
price: Math.ceil(Math.random() * 100),
isDan: index % 2 === 0
}
})
}
})
tabbar.push({
name: '附加费',
foods: [{
name: "小费",
price: Math.ceil(Math.random() * 10),
chooseNumber: 0,
isDan: true
},
{
name: "打包费",
price: 1,
chooseNumber: 0,
isDan: true
}
]
})
const data = reactive({
scrollTop: 0, //tab标题的滚动条位置
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
tabbar: tabbar,
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
timer: null, // 定时器
topZhanwei: 136 + 24
})
onReady(() => {
getMenuItemTop()
})
// 点击左边的栏目切换
async function swichMenu(index) {
if (data.arr.length == 0) {
await getMenuItemTop();
}
if (index == data.current) return;
data.scrollRightTop = data.oldScrollTop;
nextTick(function() {
data.scrollRightTop = data.arr[index] + data.topZhanwei;
data.current = index;
leftMenuStatus(index);
})
}
// 获取一个目标元素的高度
function getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({
size: true
}, res => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
getElRect(elClass);
}, 10);
return;
}
data[dataVal] = res.height;
resolve();
}).exec();
})
}
// 观测元素相交状态
async function observer() {
tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
// 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
// 如果跟.right-box底部相交就动态设置左边栏目的活动状态
observer.relativeTo('.right-box', {
top: 0
}).observe('#item' + index, res => {
if (res.intersectionRatio > 0) {
let id = res.id.substring(4);
leftMenuStatus(id);
}
})
})
}
// 设置左边菜单的滚动状态
async function leftMenuStatus(index) {
data.current = index;
// 如果为0意味着尚未初始化
if (data.menuHeight == 0 || data.menuItemHeight == 0) {
await getElRect('menu-scroll-view', 'menuHeight');
await getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
data.scrollTop = index * data.menuItemHeight + data.menuItemHeight / 2 - data.menuHeight / 2;
}
// 获取右边菜单每个item到顶部的距离
function getMenuItemTop() {
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
// 如果节点尚未生成rects值为[](因为用selectAll所以返回的是数组),循环调用执行
if (!rects.length) {
setTimeout(() => {
getMenuItemTop();
}, 10);
return;
}
rects.forEach((rect) => {
// 这里减去rects[0].top是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
data.arr.push(rect.top - rects[0].top);
resolve();
})
}).exec()
})
}
// 右边菜单滚动
async function rightScroll(e) {
data.oldScrollTop = e.detail.scrollTop;
if (data.arr.length == 0) {
await getMenuItemTop();
}
if (data.timer) return;
if (!data.menuHeight) {
await getElRect('menu-scroll-view', 'menuHeight');
}
setTimeout(() => { // 节流
data.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + data.menuHeight / 2 + data.topZhanwei / 2;
for (let i = 0; i < data.arr.length; i++) {
let height1 = data.arr[i];
let height2 = data.arr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
leftMenuStatus(i);
return;
}
}
}, 10)
}
onLoad(() => {
init()
})
</script>
<style lang="scss" scoped>
.choose-user {
background: #F9F9F9;
padding: 22rpx 28rpx;
}
.search {
padding-right: 28rpx;
}
.icon-saoma {
margin-left: 20rpx;
width: 34rpx;
height: 32rpx;
}
$u-tips-color: $my-main-color;
$u-primary: $my-main-color;
$u-main-color: $my-main-color;
.u-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.headeimg {
width: 60rpx;
height: 60rpx;
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-search-inner {
// background-color: rgb(234, 234, 234);
background-color: #fff;
border-radius: 100rpx;
display: flex;
align-items: center;
padding: 10rpx 16rpx;
}
.u-search-text {
font-size: 26rpx;
color: $u-tips-color;
margin-left: 10rpx;
}
.u-tab-view {
width: 178rpx;
height: 100%;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 1;
}
.u-tab-item-active {
position: relative;
color: #000;
font-size: 30rpx;
font-weight: 600;
background: #fff;
}
.u-tab-item-active::before {
content: "";
position: absolute;
border-left: 4px solid $u-primary;
left: 0;
top: 0;
bottom: 0;
}
.u-tab-view {
height: 100%;
}
.addCai {
width: 250rpx;
height: 272rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
box-sizing: border-box;
border: 4rpx solid #90BDF6;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.lingshi {
width: 250rpx;
height: 136px;
background: #FFFFFF;
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 4rpx solid #90BDF6;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 24px;
}
.right-box {
width: 572rpx;
// background-color: rgb(250, 250, 250);
background-color: rgb(255, 255, 255);
}
.page-view {
// padding: 24rpx 28rpx 24rpx 24rpx;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
border-radius: 8rpx;
}
.class-item:last-child {
min-height: 100vh;
}
.item-title {
font-size: 26rpx;
color: $u-main-color;
font-weight: bold;
}
.item-menu-name {
font-weight: normal;
font-size: 24rpx;
color: $u-main-color;
}
.item-container {
display: flex;
flex-wrap: wrap;
margin-top: 24rpx;
}
.thumb-box {
margin-right: 24rpx;
margin-bottom: 24rpx;
}
.item-menu-image {
width: 120rpx;
height: 120rpx;
}
</style>

View File

@@ -0,0 +1,407 @@
<template>
<view class="u-flex u-flex-col u-row-between page">
<view class="top bg-fff w-full">
<view class="u-flex u-row-between choose-user" @tap="chooseUser">
<view>选择用户</view>
<view class="u-flex">
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="search u-flex u-col-center ">
<view class="u-flex-1">
<uni-search-bar bgColor="#F9F9F9" cancelButton="none" placeholder="搜索店内商品" @confirm="search"
:focus="true" v-model="searchValue">
</uni-search-bar>
</view>
<view class="u-flex">
<image src="/pagesCreateOrder/static/images/icon-saoma.svg" class="icon-saoma" mode=""></image>
</view>
</view>
</view>
<view class="list u-flex w-full u-flex-1">
<view class="category u-flex" :style="computedCatrgoryStyle">
<scroll-view scroll-with-animation class="scroll-view_style menu-scroll-view" :scroll-into-view="category.intoviewId"
scroll-y="true" :scroll-top="category.scrollTop" :style="{height:listHeight+'px'}">
<view>
<view :id="'category'+index" class="item u-tab-item" @tap="changeCategoryActive(index)"
:class="{active:index==category.active}" v-for="(item,index) in category.list" :key="index">
{{item.title}}
</view>
</view>
</scroll-view>
</view>
<view class="goods-list u-flex-1 u-p-l-24">
<scroll-view @scroll="goodsScroll" scroll-with-animation class="scroll-view_style"
:scroll-top="goods.scrollTop" :scroll-into-view="goods.intoviewId" scroll-y="true"
:style="{height:listHeight+'px'}">
<view class="">
<view class="item" :class="{active:index==category.active}" v-for="(item,index) in goods.list"
:key="index">
<view class="color-main">{{item.title}}</view>
<view class="u-m-t-24 u-m-b-24 u-flex u-flex-wrap">
<view :id="'goods'+index" class="u-m-r-24 u-m-b-24 class-item"
v-for="(goodsItem,goodsIndex) in item.list" :key="goodsIndex">
<goods-item @chooseGuige="chooseGuige($event,index)" @add="goodsAdd($event,index)" @reduce="goodsReduce($event,index)"
:index="goodsIndex" :data="goodsItem"></goods-item>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<view class="bottom w-full">
<my-car :data="cars"></my-car>
</view>
<!-- 选择规格 -->
<my-model ref="guigeModel" :title="guigeModelData.title">
<template #desc>
<view v-for="(item,index) in guigeModelData.chooseGoods.skus" :key="index">
</view>
</template>
</my-model>
</view>
</template>
<script setup>
import {
onLoad,
onReady,
onShow,
onPageScroll,
onPullDownRefresh
} from '@dcloudio/uni-app';
import myModel from '@/components/my-components/my-model.vue'
import {
computed,
reactive,
ref,nextTick
} from 'vue';
import myCar from './components/car'
import goodsItem from './components/goods-item'
const guigeModel=ref(null)
const guigeModelData=reactive({
title:'',
chooseGoods:{
skus:[{
title:'',
skds:[]
}]
}
})
function chooseGuige(listindex, index){
const $goods=goods.list[index].list[listindex]
guigeModelData.title=$goods.title
guigeModel.value.open()
}
let searchValue = ref('')
const categoryArr = new Array(20).fill(1).map((v, index) => {
if (index % 2 === 0) {
return {
title: '围炉茶煮'
}
} else {
return {
title: '主食'
}
}
})
let pageHeight = ref(0)
const category = reactive({
list: categoryArr,
active: 0,
height: 0,
top: 0,
bottom: 0,
statusbarHeight: 0,
intoviewId: '',
scrollTop: 0
})
const goodslist = new Array(20).fill(1).map((v, index) => {
if (index % 2 === 0) {
return {
title: '围炉茶煮' + index,
list: new Array(10).fill(1).map((g, gIndex) => {
return {
id:gIndex,
title: '拿铁咖啡' + gIndex,
price: 28,
isDan: gIndex % 2 == 0,
chooseNumber: 0
}
})
}
} else {
return {
title: '主食' + index,
list: new Array(10).fill(1).map((g, gIndex) => {
return {
id:10000+gIndex,
title: '意大利面' + gIndex,
price: 28,
isDan: gIndex % 2 == 0,
chooseNumber: 0
}
})
}
}
})
let goods = reactive({
intoviewId: '',
scrollTop: 0,
list: goodslist
})
const cars=reactive([])
function chooseUser() {
goods.intoviewId = 'goods' + 5
}
function goodsAdd(listindex, index) {
const $goods=goods.list[index].list[listindex]
const newval =$goods.chooseNumber + 1
$goods.chooseNumber = newval
let item=cars.find(v=>v.id==$goods.id)
if(item){
item.number+=1
}else{
cars.push({...$goods,number:1})
}
}
function goodsReduce(listindex, index) {
const $goods = goods.list[index].list[listindex]
const newval = $goods.chooseNumber - 1
$goods.chooseNumber = newval <= 0 ? 0 : newval
}
async function changeCategoryActive(index) {
if(rightArr.length == 0) {
await getMenuItemTop();
}
if (index == category.active) return;
goods.scrollTop =oldScrollTop;
nextTick (function(){
goods.scrollTop = rightArr[index];
category.active = index;
leftMenuStatus(index);
})
}
let oldScrollTop=0;
let rightArr=[]
// 获取右边菜单每个item到顶部的距离
function getMenuItemTop() {
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
// 如果节点尚未生成rects值为[](因为用selectAll所以返回的是数组),循环调用执行
if(!rects.length) {
setTimeout(() => {
getMenuItemTop();
}, 10);
return ;
}
rects.forEach((rect) => {
// 这里减去rects[0].top是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
rightArr.push(rect.top - rects[0].top);
resolve();
})
}).exec()
})
}
let timer=null
let menuHeight=0 // 左边菜单的高度
// 获取一个目标元素的高度
function getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({
size: true
}, res => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
getElRect(elClass);
}, 10);
return;
}
if(dataVal==='menuHeight'){
menuHeight=res.height
}
if(dataVal==='menuItemHeight'){
menuItemHeight=res.height
}
resolve();
}).exec();
})
}
let menuItemHeight=0
// 设置左边菜单的滚动状态
async function leftMenuStatus(index) {
category.active = index;
// 如果为0意味着尚未初始化
if (menuHeight == 0 || menuItemHeight == 0) {
await getElRect('menu-scroll-view', 'menuHeight');
await getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
category.scrollTop = index * menuItemHeight + menuItemHeight / 2 - menuHeight / 2;
}
async function goodsScroll(e) {
oldScrollTop = e.detail.scrollTop;
if (rightArr.length == 0) {
await getMenuItemTop();
}
if (timer) return;
if (!menuHeight) {
await getElRect('menu-scroll-view', 'menuHeight');
}
setTimeout(() => { // 节流
timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + menuHeight / 2;
for (let i = 0; i < rightArr.length; i++) {
let height1 = rightArr[i];
let height2 = rightArr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
leftMenuStatus(i);
return;
}
}
}, 10)
}
onReady(() => {
initCategory()
getMenuItemTop()
})
function initCategory() {
uni.getSystemInfo({
success: (res) => {
pageHeight.value = res.windowHeight
const screenHeight = res.windowHeight; // 屏幕可用高度
const topinfo = uni.createSelectorQuery().select(".top");
const bottominfo = uni.createSelectorQuery().select(".bottom");
topinfo.boundingClientRect(function(data) { //data - 各种参数
category.top = data.height + category.statusbarHeight
}).exec()
bottominfo.boundingClientRect(function(data) { //data - 各种参数
category.bottom = data.height
}).exec()
}
});
}
const listHeight = computed(() => {
return pageHeight.value - category.bottom - category.top
})
const computedCatrgoryStyle = computed(() => {
return `
top:${category.top}px;
bottom:${category.bottom}px;
`
})
function search() {
}
</script>
<style lang="scss" scoped>
/* 针对微信小程序的滚动条样式 */
::v-deep.scroll-view_style::-webkit-scrollbar {
width: 8rpx;
}
::v-deep.scroll-view_style::-webkit-scrollbar-thumb {
background: #90BDF6;
border-radius: 5px;
/* 设置滚动条圆角 */
}
::v-deep.scroll-view_style::-webkit-scrollbar-track {
background-color: #f1f1f1;
/* 设置滚动条轨道颜色 */
}
.page {
height: calc(100vh - 44px);
}
.goods-list {
box-sizing: border-box;
width: 572rpx;
}
.category {
box-sizing: border-box;
// position: fixed;
width: 178rpx;
// left: 0;
.item {
padding: 36rpx 32rpx;
position: relative;
background: #F9F9F9;
transition: all .2s ease-in-out;
&.active {
background: #fff;
}
&.active::after {
background-color: $my-main-color;
}
&::after {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 8rpx;
}
}
}
.choose-user {
background: #F9F9F9;
padding: 22rpx 28rpx;
}
.search {
padding-right: 28rpx;
}
.icon-saoma {
margin-left: 20rpx;
width: 34rpx;
height: 32rpx;
}
</style>

View File

@@ -0,0 +1,996 @@
<template>
<view class="u-wrap">
<view class="top bg-fff w-full">
<view class="u-flex u-row-between choose-user" @tap="chooseUser">
<view>
<view v-if="!data.vipUser.id">选择用户</view>
<view class="u-flex" v-else>
<view class="headeimg">
<image class="img" :src="data.vipUser.headImg" mode=""></image>
</view>
<view class="u-m-l-20">{{data.vipUser.nickName}}</view>
<view class="color-main u-m-l-10 u-font-24">{{data.vipUser.isVip?'会员':'' }}</view>
<view class="u-font-24 u-m-l-30"><text>余额</text><text
class="color-main">{{data.vipUser.amount}}</text></view>
<view class="u-font-24 u-m-l-30"><text>积分</text><text
class="color-main">{{data.vipUser.totalScore}}</text></view>
</view>
</view>
<view class="u-flex">
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="search u-flex u-col-center ">
<view class="u-flex-1">
<uni-search-bar bgColor="#F9F9F9" cancelButton="none" placeholder="搜索店内商品" @confirm="search"
:focus="true" v-model="searchValue">
</uni-search-bar>
</view>
<view class="u-flex">
<image src="/pagesCreateOrder/static/images/icon-saoma.svg" class="icon-saoma" mode=""></image>
</view>
</view>
</view>
<view class="u-menu-wrap">
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="data.scrollTop"
:scroll-into-view="data.itemId">
<view v-for="(item,index) in data.tabbar" :key="index" class="u-tab-item"
:class="[data.current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{item.name}}</text>
</view>
</scroll-view>
<scroll-view :scroll-top="data.scrollRightTop" scroll-y scroll-with-animation class="right-box"
@scroll="rightScroll">
<view class="page-view u-p-l-24">
<view class="lingshi" @tap="toLinshi">
<uni-icons type="plus-filled" size="24" :color="color.ColorMain"></uni-icons>
<view class="u-m-t-24 color-main">临时菜</view>
</view>
<view class="class-item" :id="'item' + index" v-for="(item , index) in data.tabbar" :key="index">
<view class="item-title">
<text>{{item.name}}</text>
</view>
<view class="item-container">
<view class="thumb-box" v-for="(goodsItem, goodsIndex) in item.foods" :key="goodsIndex">
<goods-item @chooseGuige="chooseGuige($event,index)"
@add="goodsUpdate($event,index,true)" @reduce="goodsUpdate($event,index,false)"
:index="goodsIndex" :data="goodsItem"></goods-item>
</view>
<template v-if="item.name==='附加费'">
<view class="addCai" @tap="surchargeShow">
<uni-icons type="plus-filled" size="24" :color="color.ColorMain"></uni-icons>
<view class="u-m-t-24 color-main">自定义添加</view>
</view>
</template>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="bottom w-full">
<my-car @updateNumber="carsNumberChange" :user="data.vipUser" :masterId="data.masterId"
:table="data.table"
:data="cars" @clear="onClearCart"></my-car>
</view>
<!-- 选择规格 -->
<guige-model @update-sku="updateSkuSel" @confirm="guigeConfirm" ref="chooseGuigeModel" :title="guigeModelData.title"
:sku-map="guigeModelData.chooseGoods.skuMap" :skus="guigeModelData.chooseGoods.skus"></guige-model>
<!-- 添加附加费 -->
<my-surcharge @confirm="surchargeConfirm" ref="surcharge" title="添加附加费"></my-surcharge>
</view>
</template>
<script setup>
import _ from 'lodash';
import * as Api from '@/http/yskApi/Instead.js'
import {
$tbShopCategory
} from '@/http/yskApi/goods.js'
import util from './util.js';
import classifyData from './classify.data.js';
import color from '@/commons/color.js';
import guigeModel from './components/guige'
import goodsItem from './components/goods-item'
import mySurcharge from './components/surcharge'
import {
onLoad,
onReady,
onShow,
onPageScroll,
onPullDownRefresh
} from '@dcloudio/uni-app';
import {
onBeforeUnmount,
computed,
reactive,
ref,
nextTick
} from 'vue';
import myCar from './components/car'
import go from '@/commons/utils/go.js';
import infoBox from '@/commons/utils/infoBox.js';
const cars = reactive([])
const data = reactive({
scrollTop: 0, //tab标题的滚动条位置
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
tabbar: [],
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
timer: null, // 定时器
topZhanwei: 136 + 24,
// 选择用户
vipUser: {
id: "",
},
//餐桌号
masterId: "",
table: {
tableId: ""
},
})
function setTabBar(category, goods, cars) {
const goodsCategoryMap = goods.reduce((prve, cur) => {
if (!prve.hasOwnProperty(cur.categoryId)) {
prve[cur.categoryId] = []
}
prve[cur.categoryId].push(cur)
return prve
}, {})
const chooseGoodsNumberMap = cars.reduce((prve, cur) => {
if (!prve.hasOwnProperty(cur.productId)) {
prve[cur.productId] = 0
}
prve[cur.productId] += cur.number
return prve
}, {})
let tabbar = category.map(v => {
const foods = goodsCategoryMap[v.id] || []
return {
...v,
foods: foods.map((fgoods, index) => {
return {
...fgoods,
chooseNumber: chooseGoodsNumberMap[fgoods.id],
price: fgoods.lowPrice,
isDan: fgoods.typeEnum !== 'sku'
}
})
}
})
tabbar.push({
name: '附加费',
foods: [{
name: "小费",
price: Math.ceil(Math.random() * 10),
chooseNumber: 0,
isDan: true
},
{
name: "打包费",
price: 1,
chooseNumber: 0,
isDan: true
}
]
})
tabbar = tabbar.filter(v => {
return v.foods.length
})
data.tabbar = tabbar
}
//request类
//获取分类
function getCategory(par = {
page: 0,
size: 300
}) {
return $tbShopCategory(par)
}
//获取商品列表
function getGoods(par = {
page: 0,
size: 300
}) {
return Api.getGoodsLists(par)
}
//获取购物车数据
function getCart(par = {
page: 0,
size: 300,
masterId: data.masterId,
tableId: data.table.tableId
}) {
return Api.getCart(par)
}
//获取取餐码
function getMasterId() {
return Api.$getMasterId({
tableId: data.table.tableId
})
}
//清空购物车
function clearCart() {
return Api.$clearCart({
masterId: data.masterId,
tableId: data.table.tableId
})
}
//加入购物车
function addCart(par) {
const submitPar = {
masterId: data.masterId,
tableId: data.table.tableId,
isPack: false,
num: 1,
productId: '',
skuId: '',
vipUserId: data.vipUser.id
}
Object.assign(submitPar, par)
return Api.addCart(submitPar)
}
//更新购物车
function updateCartGoods(par) {
const submitPar = {
cartId: '',
masterId: data.masterId,
tableId: data.table.tableId,
isPack: false,
num: 1,
productId: '',
skuId: '',
vipUserId: data.vipUser.id
}
Object.assign(submitPar, par)
return Api.$updateCart(submitPar)
}
//删除购物车某商品
function removeCartGoods(par) {
const submitPar = {
cartId: '',
masterId: data.masterId,
tableId: data.table.tableId,
}
Object.assign(submitPar, par)
return Api.$removeCart(submitPar)
}
//更新选择用户
function setUser(par) {
const submitPar = {
masterId: data.masterId,
tableId: data.table.tableId,
vipUserId: data.vipUser.id ? data.vipUser.id : '',
type: data.vipUser.id ? 0 : 1 //0 设置 1 取消
}
Object.assign(submitPar, par)
return Api.$setUser(submitPar)
}
//点击清空购物车
async function onClearCart() {
await clearCart()
cars.length = 0
for (let i in data.tabbar) {
for (let k in data.tabbar[i].fgoods) {
data.tabbar[i].fgoods[k].chooseNumber = 0
}
}
}
async function init() {
const {
masterId
} = await getMasterId()
data.masterId = masterId
const cartRes = await getCart()
cars.length = 0
for (let i in cartRes.records) {
cars.push(cartRes.records[i])
}
const categoryRes = await getCategory()
const category = categoryRes.content.reduce((prve, cur) => {
prve.push({
...cur,
childrenList: null
});
return [...prve, ...cur.childrenList];
}, []);
console.log(category);
const goodsRes = await getGoods()
const goods = goodsRes.records.filter((v) => {
let isShow = true;
if (v.typeEnum !== "sku") {
isShow = v.specList.length >= 1;
}
return isShow;
});
setTabBar(category, goods, cars)
}
// 监听选择用户事件
const surcharge = ref(null)
function surchargeConfirm(e) {
data.tabbar[data.tabbar.length - 1].foods.unshift({
...e,
chooseNumber: 0
})
}
function surchargeShow() {
surcharge.value.open()
}
let searchValue = ref('')
function search() {
}
function chooseUser() {
go.to('PAGES_CHOOSE_USER')
}
function toLinshi() {
go.to('PAGES_ADD_TEMP_CUISINE')
}
const chooseGuigeModel = ref(null)
const guigeModelData = reactive({
title: '',
chooseGoods: {
skuMap: {},
item: '',
skus: []
}
})
// 返回当前选中商品skuList
function returnSelGoodsSkuList(skuList, specSnap) {
const specSnapArr = specSnap ? specSnap.split(",") : [];
const result = skuList.map((v, index) => {
const values = v.value.split(",");
return {
...v,
valueArr: values,
sel: specSnap ? specSnapArr[index] : "",
values: values.map((name) => {
return {
name,
disabled: false
};
}),
};
});
return result
}
//返回当前选中商品skuMap
function returnSelGoodsSkuMap(specList) {
const skuMap = {}
for (let i in specList) {
skuMap[specList[i].specSnap] = specList[i];
}
return skuMap
}
//多规格商品弹窗时,找到默认可以下单的规格商品
function findGoods(skuList = [], goodsListMap = {}, specList) {
const skuMapNumber = skuList.reduce((prve, cur) => {
for (let i in cur.valueArr) {
prve[cur.valueArr[i]] = i;
}
return prve;
}, {});
const canBudyGoods = specList
.filter((v) => util.isCanBuy(v))
.sort((a, b) => {
const aNumber = a.specSnap.split(",").reduce((prve, cur) => {
return prve + skuMapNumber[cur];
}, 0);
const bNumber = b.specSnap.split(",").reduce((prve, cur) => {
return prve + skuMapNumber[cur];
}, 0);
return aNumber - bNumber;
});
return canBudyGoods[0];
}
//设置商品默认选中,规格禁止以及选中
function setSkugoodsDefaultInit(goods, skuList, skuMap, specList) {
guigeModelData.chooseGoods.item = goods
guigeModelData.chooseGoods.skus = skuList
guigeModelData.chooseGoods.skuMap = skuMap
const skuGoods = findGoods(skuList, skuMap, specList);
console.log(skuList, skuMap);
console.log(skuGoods);
if (skuGoods) {
// this.skuGoods.data = skuGoods;
// this.skuGoods.number = skuGoods.suit || 1;
skuGoods.specSnap.split(",").map((v, index) => {
guigeModelData.chooseGoods.skus[index].sel = v;
});
}
console.log(guigeModelData.chooseGoods.skus);
setTagDisabled();
}
//更新选中规格
function updateSkuSel(skusIndex, skdName) {
const skuList = guigeModelData.chooseGoods.skus
const skuMap = guigeModelData.chooseGoods.skuMap
guigeModelData.chooseGoods.skus[skusIndex].sel = skdName
const specSnap = guigeModelData.chooseGoods.skus.reduce((prve, cur) => {
prve.push(cur.sel)
return prve
}, []).join()
setTagDisabled();
// const canChooseGoods = skuList.every((v) => v.sel);
// if (canChooseGoods) {
// const skuGoods =skuMap[specSnap];
// guigeModelData.chooseGoods.item = skuGoods
// }
}
//设置规格按钮的禁止状态
function setTagDisabled() {
const skuList = guigeModelData.chooseGoods.skus
const skuMap = guigeModelData.chooseGoods.skuMap
const selArr = skuList.reduce((prve, cur) => {
if (cur.sel) {
prve.push(cur.sel);
} else {}
return prve;
}, []);
console.log(selArr);
let selArrAllGroup = util.generateCombinations(selArr, selArr.length - 1);
console.log(selArrAllGroup);
const matchArr = [];
for (let key in skuMap) {
const goods = skuMap[key];
const keyArr = key.split(",");
for (let spe of selArrAllGroup) {
if (util.arrayContainsAll(keyArr, spe)) {
matchArr.push(goods);
break;
}
}
}
//全部规格都已下架
if (!matchArr.length) {
for (let k in skuList) {
for (let i in skuList[k].values) {
skuList[k].values[i].disabled = true
}
}
return;
}
const includeSkuMap = matchArr.reduce((prve, cur) => {
const speArr = cur.specSnap.split(",");
for (let i of speArr) {
if (!prve.hasOwnProperty("i")) {
prve[i] = matchArr
.filter((v) => v.specSnap.match(i))
.every((v) => {
return util.isCanBuy(v)
});
}
}
return prve;
}, {});
for (let i in includeSkuMap) {
for (let k in skuList) {
const index = skuList[k].valueArr.findIndex((val) => val === i);
if (index !== -1) {
skuList[k].values[index].disabled = includeSkuMap[i]
}
}
}
}
function chooseGuige(foodsindex, index) {
const $goods = data.tabbar[index].foods[foodsindex]
guigeModelData.title = $goods.name
const specList = $goods.specList;
const tagSnap = JSON.parse($goods.skuResult.tagSnap)
const skuMap = returnSelGoodsSkuMap(specList)
const skuList = returnSelGoodsSkuList(tagSnap)
setSkugoodsDefaultInit($goods, skuList, skuMap, specList)
chooseGuigeModel.value.open()
}
async function guigeConfirm(sku, num) {
const goods = guigeModelData.chooseGoods.item
const skuId = sku.id
const productId = goods.id
const res = findGoodsInCar(goods, skuId)
if (res) {
//更新
const {
index
} = res
const carGoods = cars[index]
const cartId = carGoods.id
const newNumber = carGoods.number * 1 + num
const {
number
} = await updateCartGoods({
num: newNumber,
cartId,
productId,
skuId
})
carGoods.number = number
} else {
//添加
const cartGoods = await addCart({
num,
productId,
skuId
})
cars.push({
...cartGoods,
specSnap: sku.specSnap
})
}
}
//购物车商品数量改变
async function carsNumberChange(e) {
const {
num,
index,
goods
} = e
const {
productId,
categoryId,
skuId
} = goods
const cartId = goods.id
const tabbarIndex = data.tabbar.findIndex(v => v.id == categoryId)
const $goods = data.tabbar[tabbarIndex].foods.find(v => v.id == productId)
if (num === 0) {
//移除
await removeCartGoods({
cartId
})
cars.splice(index, 1)
$goods.chooseNumber = 0
return
}
await updateCartGoods({
num,
cartId,
productId,
skuId
})
cars[index].number = num
if ($goods) {
$goods.chooseNumber = num
}
}
// 找到该规格商品在购物车中是否存在并返回index值以及对应的数据
function findGoodsInCar($goods, skuId) {
const productId = $goods.id
const goodsInCarIndex = cars.findIndex((carsGoods) => {
return carsGoods.skuId == skuId && carsGoods.productId == productId;
});
const carGoods = cars[goodsInCarIndex]
return carGoods ? {
index: goodsInCarIndex,
carGoods
} : false
}
async function goodsUpdate(foodsindex, index, isAdd) {
const $goods = data.tabbar[index].foods[foodsindex]
if ($goods.isDan) {
//单规格
const goodsInCarIndex = cars.findIndex((carsGoods) => {
return carsGoods.skuId == $goods.specList[0].id && carsGoods.productId == $goods.id;
});
const productId = $goods.id
const skuId = $goods.specList[0].id
if (goodsInCarIndex !== -1) {
//更新
const carGoods = cars[goodsInCarIndex]
const cartId = carGoods.id
const step = isAdd ? 1 : -1
const num = carGoods.number * 1 + step
if (num === 0) {
//移除
cars.splice(goodsInCarIndex, 1)
$goods.chooseNumber = 0
return await removeCartGoods({
cartId
})
}
const {
number
} = await updateCartGoods({
num,
cartId,
productId,
skuId
})
carGoods.number = number
$goods.chooseNumber = number
} else {
//增加
const num = 1
const cartGoods = await addCart({
num,
productId,
skuId
})
$goods.chooseNumber = num
cars.push(cartGoods)
}
return
}
}
onReady(() => {
getMenuItemTop()
})
let isTabClickOver=true
// 点击左边的栏目切换
async function swichMenu(index) {
if (data.arr.length == 0) {
await getMenuItemTop();
}
if (index == data.current) return;
isTabClickOver=false;
data.scrollRightTop = data.oldScrollTop;
nextTick(function() {
data.scrollRightTop = data.arr[index] + data.topZhanwei;
data.current = index;
leftMenuStatus(index);
})
}
// 获取一个目标元素的高度
function getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({
size: true
}, res => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
getElRect(elClass);
}, 10);
return;
}
data[dataVal] = res.height;
resolve();
}).exec();
})
}
// 观测元素相交状态
async function observer() {
tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
// 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
// 如果跟.right-box底部相交就动态设置左边栏目的活动状态
observer.relativeTo('.right-box', {
top: 0
}).observe('#item' + index, res => {
if (res.intersectionRatio > 0) {
let id = res.id.substring(4);
leftMenuStatus(id);
}
})
})
}
// 设置左边菜单的滚动状态
async function leftMenuStatus(index) {
if(!isTabClickOver){
return
}
data.current = index;
// 如果为0意味着尚未初始化
if (data.menuHeight == 0 || data.menuItemHeight == 0) {
await getElRect('menu-scroll-view', 'menuHeight');
await getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
data.scrollTop = index * data.menuItemHeight + data.menuItemHeight / 2 - data.menuHeight / 2;
}
// 获取右边菜单每个item到顶部的距离
function getMenuItemTop() {
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
// 如果节点尚未生成rects值为[](因为用selectAll所以返回的是数组),循环调用执行
if (!rects.length) {
setTimeout(() => {
getMenuItemTop();
}, 10);
return;
}
rects.forEach((rect) => {
// 这里减去rects[0].top是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
data.arr.push(rect.top - rects[0].top);
resolve();
})
}).exec()
})
}
// 右边菜单滚动
async function rightScroll(e) {
data.oldScrollTop = e.detail.scrollTop;
if (data.arr.length == 0) {
await getMenuItemTop();
}
if (data.timer) return;
if (!data.menuHeight) {
await getElRect('menu-scroll-view', 'menuHeight');
}
setTimeout(() => { // 节流
data.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + data.menuHeight / 2 + data.topZhanwei / 2;
for (let i = 0; i < data.arr.length; i++) {
let height1 = data.arr[i];
let height2 = data.arr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
if(isTabClickOver){
leftMenuStatus(i);
}else{
isTabClickOver=true
}
return;
}
}
}, 10)
}
function watchChooseuser() {
uni.$off('choose-user')
uni.$on('choose-user', (user) => {
console.log(user);
data.vipUser = user ? user : {
id: ''
}
setUser()
})
}
onBeforeUnmount(() => {
})
onShow(()=>{
watchChooseuser()
})
onLoad((opt) => {
console.log(opt)
Object.assign(data.table,opt)
if (!opt.tableId) {
infoBox.showErrorToast('暂不支持不选择台桌下载,请从桌台点餐')
return setTimeout(() => {
go.back()
}, 1500)
}
init()
})
</script>
<style lang="scss" scoped>
.choose-user {
background: #F9F9F9;
padding: 22rpx 28rpx;
}
.search {
padding-right: 28rpx;
}
.icon-saoma {
margin-left: 20rpx;
width: 34rpx;
height: 32rpx;
}
$u-tips-color: $my-main-color;
$u-primary: $my-main-color;
$u-main-color: $my-main-color;
.u-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.headeimg {
width: 60rpx;
height: 60rpx;
display: flex;
background-color: #eee;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 60rpx;
height: 60rpx;
}
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-search-inner {
// background-color: rgb(234, 234, 234);
background-color: #fff;
border-radius: 100rpx;
display: flex;
align-items: center;
padding: 10rpx 16rpx;
}
.u-search-text {
font-size: 26rpx;
color: $u-tips-color;
margin-left: 10rpx;
}
.u-tab-view {
width: 178rpx;
height: 100%;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 1;
}
.u-tab-item-active {
position: relative;
color: #000;
font-size: 30rpx;
font-weight: 600;
background: #fff;
}
.u-tab-item-active::before {
content: "";
position: absolute;
border-left: 4px solid $u-primary;
left: 0;
top: 0;
bottom: 0;
}
.u-tab-view {
height: 100%;
background-color: #f6f6f6;
}
.addCai {
width: 250rpx;
height: 272rpx;
border-radius: 8rpx 8rpx 8rpx 8rpx;
box-sizing: border-box;
border: 4rpx solid #90BDF6;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.lingshi {
width: 250rpx;
height: 136px;
background: #FFFFFF;
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 4rpx solid #90BDF6;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 24px;
}
.right-box {
width: 572rpx;
// background-color: rgb(250, 250, 250);
background-color: rgb(255, 255, 255);
}
.page-view {
// padding: 24rpx 28rpx 24rpx 24rpx;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
border-radius: 8rpx;
}
.class-item:last-child {
min-height: 100vh;
}
.item-title {
font-size: 26rpx;
color: $u-main-color;
font-weight: bold;
}
.item-menu-name {
font-weight: normal;
font-size: 24rpx;
color: $u-main-color;
}
.item-container {
display: flex;
flex-wrap: wrap;
margin-top: 24rpx;
}
.thumb-box {
margin-right: 24rpx;
margin-bottom: 24rpx;
}
.item-menu-image {
width: 120rpx;
height: 120rpx;
}
</style>

View File

@@ -0,0 +1,52 @@
//判断商品是否可以下单
export function isCanBuy(goods,isStock) {
return goods.isGrounding && goods.isPauseSale == 0 && (isStock?goods.stockNumber > 0:true) ;
}
// 一个数组是否包含另外一个数组全部元素
function arrayContainsAll(arr1, arr2) {
for (let i = 0; i < arr2.length; i++) {
if (!arr1.includes(arr2[i])) {
return false;
}
}
return true;
}
//n项 n-1项组合生成全部结果
function generateCombinations(arr, k) {
let result = [];
function helper(index, current) {
if (current.length === k) {
result.push(current.slice()); // 使用slice()来避免直接修改原始数组
} else {
for (let i = index; i < arr.length; i++) {
current.push(arr[i]); // 将当前元素添加到组合中
helper(i + 1, current); // 递归调用,索引增加以避免重复选择相同的元素
current.pop(); // 回溯,移除当前元素以便尝试其他组合
}
}
}
helper(0, []); // 从索引0开始初始空数组作为起点
return result;
}
function returnReverseVal(val, isReturnString = true) {
const isBol = typeof val === "boolean";
const isString = typeof val === "string";
let reverseNewval = "";
if (isBol) {
reverseNewval = !val;
}
if (isString) {
reverseNewval = val === "true" ? "false" : "true";
}
return reverseNewval;
}
export default{
isCanBuy,arrayContainsAll,generateCombinations,returnReverseVal
}