422 lines
12 KiB
Vue
422 lines
12 KiB
Vue
|
|
<script lang="ts" setup>
|
|||
|
|
import { safeAreaInsets } from '@/utils/systemInfo'
|
|||
|
|
import {pageServiceAllByType ,pagePhysicalAllByType } from '@/api/goods'
|
|||
|
|
import { onMounted, ref,nextTick ,computed} from 'vue'
|
|||
|
|
import { getRect, isArray } from 'wot-design-uni/components/common/util'
|
|||
|
|
import { useUserStore } from '@/store/user';
|
|||
|
|
import { API_BASE_URL } from "@/config/setting";
|
|||
|
|
import { useCartStore } from '@/store/cart';
|
|||
|
|
import CartEdit from "./components/cart-edit.vue";
|
|||
|
|
import PositionEdit from "@/components/position-edit/index.vue";
|
|||
|
|
import bus, { EVENT_KEY } from '@/utils/bus'
|
|||
|
|
const userStore = useUserStore();
|
|||
|
|
const cartStore = useCartStore();
|
|||
|
|
|
|||
|
|
bus.on(EVENT_KEY.REFRESH_SHOPPING_CART, () => {
|
|||
|
|
queryAllPhysicalByType()
|
|||
|
|
store.value = uni.getStorageSync('store') || null
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const showEdit = ref(false)
|
|||
|
|
/** 当前编辑数据 */
|
|||
|
|
const current = ref<any | null>(null);
|
|||
|
|
|
|||
|
|
/** 打开编辑弹窗 */
|
|||
|
|
const openEdit = (row?: any) => {
|
|||
|
|
current.value = row ?? null;
|
|||
|
|
//多个规格
|
|||
|
|
if(row.price){
|
|||
|
|
reload(row)
|
|||
|
|
}else {
|
|||
|
|
showEdit.value = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
};
|
|||
|
|
/** 刷新购物车 */
|
|||
|
|
const reload = (data?: any) => {
|
|||
|
|
cartStore.addItem(data).then(() => {
|
|||
|
|
cartStore.getListCart().then((res: any) => {
|
|||
|
|
allSelected()
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
onShow(()=>{
|
|||
|
|
cartStore.getListCart().then((res: any) => {
|
|||
|
|
allSelected()
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 商品列表选择的数量
|
|||
|
|
const allSelected = () => {
|
|||
|
|
categories.value.forEach((item: any) => {
|
|||
|
|
item.child.forEach((child: any) => {
|
|||
|
|
const a = ref(0)
|
|||
|
|
child.numAdd = 0
|
|||
|
|
cartStore.items.forEach((cartItem:any)=>{
|
|||
|
|
if(cartItem.listId === child.listId){
|
|||
|
|
//处理单规格
|
|||
|
|
if(!cartItem.specs){
|
|||
|
|
child.numAdd = cartItem.amount
|
|||
|
|
}else {
|
|||
|
|
//处理多规格
|
|||
|
|
a.value += cartItem.amount
|
|||
|
|
child.numAdd = a.value
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
definePage({
|
|||
|
|
// 使用 type: "home" 属性设置首页,其他页面不需要设置,默认为page
|
|||
|
|
type: 'home',
|
|||
|
|
style: {
|
|||
|
|
// 'custom' 表示开启自定义导航栏,默认 'default'
|
|||
|
|
// navigationStyle: 'custom',
|
|||
|
|
navigationBarTitleText: '首页',
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const showContent = ref(true)
|
|||
|
|
//查询全部服务
|
|||
|
|
const queryAllByType = async ()=>{
|
|||
|
|
showContent.value = true
|
|||
|
|
|
|||
|
|
pageServiceAllByType({keyword:keyword.value,onOff:'1',shopId:uni.getStorageSync('store')?.id || null}).then(async (res: any) => {
|
|||
|
|
categories.value = await initListId(res.data,'service')
|
|||
|
|
showContent.value = false
|
|||
|
|
// 使用 nextTick 确保 DOM 更新完成
|
|||
|
|
await nextTick()
|
|||
|
|
await getRectAll()
|
|||
|
|
cartStore.getListCart().then((res: any) => {
|
|||
|
|
allSelected()
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
//查询全部产品
|
|||
|
|
const queryAllPhysicalByType = async ()=>{
|
|||
|
|
showContent.value = true
|
|||
|
|
pagePhysicalAllByType({keyword:keyword.value,onOff:'1',shopId:uni.getStorageSync('store')?.id || null}).then(async (res: any) => {
|
|||
|
|
categories.value = initListId(res.data,'product')
|
|||
|
|
showContent.value = false
|
|||
|
|
// 使用 nextTick 确保 DOM 更新完成
|
|||
|
|
await nextTick(() => {
|
|||
|
|
getRectAll()
|
|||
|
|
})
|
|||
|
|
cartStore.getListCart().then((res: any) => {
|
|||
|
|
allSelected()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// listId 生成唯一购物车标识符:商品id + 分类 + 商品类型 + 规格id
|
|||
|
|
const initListId = (data:any,type:string)=>{
|
|||
|
|
return data.map((item:any)=>{
|
|||
|
|
return{
|
|||
|
|
...item,
|
|||
|
|
child:item.child.map((child:any)=>{
|
|||
|
|
return{
|
|||
|
|
...child,
|
|||
|
|
type:type,
|
|||
|
|
listId: `${child.id}_${type}`
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
const getRectAll = async ()=>{
|
|||
|
|
await getRect('.category', true).then((rects) => {
|
|||
|
|
if (isArray(rects)) {
|
|||
|
|
itemScrollTop.value = rects.map((item) => item.top - 100 || 0)
|
|||
|
|
scrollTop.value = rects[active.value].top - 100 || 0
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onScroll(e) {
|
|||
|
|
const { scrollTop } = e.detail
|
|||
|
|
const threshold = 100 // 下一个标题与顶部的距离
|
|||
|
|
if (scrollTop < threshold) {
|
|||
|
|
active.value = 0
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
const index = itemScrollTop.value.findIndex((top) => top > scrollTop && top - scrollTop <= threshold)
|
|||
|
|
if (index > -1) {
|
|||
|
|
active.value = index
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
const searchType = ref<string>('产品')
|
|||
|
|
const keyword = ref<string>('')
|
|||
|
|
const menu = ref([
|
|||
|
|
{
|
|||
|
|
content: '产品',
|
|||
|
|
value: 9
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
content: '服务',
|
|||
|
|
value: 8
|
|||
|
|
}
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
//切换 商品类型 全部初始化
|
|||
|
|
function changeSearchType({ item, index }) {
|
|||
|
|
searchType.value = item.content
|
|||
|
|
active.value = 0
|
|||
|
|
keyword.value = ''
|
|||
|
|
if (item.value === 8) {
|
|||
|
|
queryAllByType()
|
|||
|
|
} else {
|
|||
|
|
queryAllPhysicalByType()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
//搜索
|
|||
|
|
const changeSearch = ()=>{
|
|||
|
|
if (searchType.value === '服务') {
|
|||
|
|
queryAllByType()
|
|||
|
|
} else {
|
|||
|
|
queryAllPhysicalByType()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
const active = ref<number>(0)
|
|||
|
|
const scrollTop = ref<number>(0)
|
|||
|
|
const itemScrollTop = ref<number[]>([])
|
|||
|
|
|
|||
|
|
const subCategories = new Array(10).fill( 0, 1)
|
|||
|
|
const categories = ref([])
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
queryAllPhysicalByType()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
function handleChange({ value }) {
|
|||
|
|
active.value = value
|
|||
|
|
scrollTop.value = itemScrollTop.value[value]
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
const getPrice = (record: any) => {
|
|||
|
|
if (!record) return
|
|||
|
|
return getSpecificationPrice(record.specificationPrices);
|
|||
|
|
};
|
|||
|
|
const getSpecificationPrice = (specifications: any[]): number | undefined => {
|
|||
|
|
if(!specifications) return
|
|||
|
|
const firstSpec = specifications[0];
|
|||
|
|
if (!firstSpec) return undefined;
|
|||
|
|
if (firstSpec.price != null && firstSpec.price !== 0) return firstSpec.price;
|
|||
|
|
if (firstSpec.child) return getSpecificationPrice(firstSpec.child);
|
|||
|
|
return undefined;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
//跳转购物车
|
|||
|
|
const goShoppingCart = ()=>{
|
|||
|
|
uni.navigateTo({url: '/pages/index/shopping-cart/index'})
|
|||
|
|
}
|
|||
|
|
//跳转详情
|
|||
|
|
const goDetail = (record: any) => {
|
|||
|
|
cartStore.itemsDetail = {...record,productId:record.id}
|
|||
|
|
|
|||
|
|
console.log(record,'???????')
|
|||
|
|
uni.navigateTo({url: '/pages/index/shopping-cart-details/index'})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
const store = ref<any | null>(uni.getStorageSync('store') || null)
|
|||
|
|
|
|||
|
|
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<!-- :style="{ marginTop: `${safeAreaInsets?.top}px` }"-->
|
|||
|
|
<view >
|
|||
|
|
<view>
|
|||
|
|
<wd-fab draggable position="right-bottom" :expandable="false">
|
|||
|
|
<template #trigger>
|
|||
|
|
<wd-badge :modelValue="cartStore.items.length" right="5" top="5">
|
|||
|
|
<view @click="goShoppingCart" class="cart-icon" style=""><wd-icon color="#000" name="cart" size="22px"></wd-icon></view>
|
|||
|
|
</wd-badge>
|
|||
|
|
</template>
|
|||
|
|
</wd-fab>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 门店信息展示 -->
|
|||
|
|
<view class="store-info" @click="userStore.showPosition = true" >
|
|||
|
|
<view class="store-name">{{ store?.name }}</view>
|
|||
|
|
<wd-icon name="translate-bold" style="margin-left: 6px" size="16px"></wd-icon>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<wd-search @change="changeSearch" @clear="changeSearch" v-model="keyword" hide-cancel>
|
|||
|
|
<template #prefix>
|
|||
|
|
<wd-popover mode="menu" :content="menu" @menuclick="changeSearchType">
|
|||
|
|
<view class="search-type">
|
|||
|
|
<span>{{ searchType }}</span>
|
|||
|
|
<wd-icon custom-class="icon-arrow" name="fill-arrow-down"></wd-icon>
|
|||
|
|
</view>
|
|||
|
|
</wd-popover>
|
|||
|
|
</template>
|
|||
|
|
</wd-search>
|
|||
|
|
<view class="wraper" v-if="categories.length > 0" >
|
|||
|
|
<wd-sidebar v-model="active" @change="handleChange">
|
|||
|
|
<wd-sidebar-item v-for="(item, index) in categories" :key="index" :value="index" :label="item.name" /></wd-sidebar>
|
|||
|
|
<scroll-view class="content" scroll-y scroll-with-animation :scroll-top="scrollTop" :throttle="false" @scroll="onScroll">
|
|||
|
|
<view v-for="(item, index) in categories" :key="index" class="category">
|
|||
|
|
<wd-cell-group :title="item.name" border>
|
|||
|
|
<wd-card v-if="item.child.length" style="margin-bottom: 0; height: 100px" type="rectangle" v-for="(cell, index) in item.child" :key="index">
|
|||
|
|
<view @click="goDetail(cell)" class="items">
|
|||
|
|
<wd-img radius="6" v-if="cell?.pictures?.length > 0" :width="70" :height="70" :src="API_BASE_URL + cell.pictures[0]">
|
|||
|
|
<template #error>
|
|||
|
|
<view class="error-wrap">加载失败</view>
|
|||
|
|
</template>
|
|||
|
|
<template #loading>
|
|||
|
|
<view class="loading-wrap">
|
|||
|
|
<wd-loading />
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
</wd-img>
|
|||
|
|
<view class="items-baseline">
|
|||
|
|
<view class="title"><wd-text color="#000" size="14px" :text="cell.name"></wd-text></view>
|
|||
|
|
<view class="price">
|
|||
|
|
<span>¥{{ cell.price ? cell.price : getPrice(cell) + ' 起' }}</span>
|
|||
|
|
<wd-text v-if="cell.underlinedPrice" :text="cell.underlinedPrice" size="11px" style="margin-left: 6px" decoration="line-through" prefix="¥" />
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<view class="cart-btn">
|
|||
|
|
<wd-badge custom-class="custom-class-badge-add" :modelValue="cell.numAdd" right="0" top="0">
|
|||
|
|
<view @click.stop="openEdit(cell)" class="cart"><wd-icon color="#F56C6C" name="add" size="14px"></wd-icon></view>
|
|||
|
|
</wd-badge>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</wd-card>
|
|||
|
|
<wd-status-tip image-size="18" v-if="item?.child?.length === 0 && !showContent" tip="暂无数据" />
|
|||
|
|
</wd-cell-group>
|
|||
|
|
</view>
|
|||
|
|
<view style="display: flex; margin: 15px" v-for="item in subCategories" :key="item">
|
|||
|
|
<wd-skeleton :loading="showContent" animation="flashed" theme="paragraph" :row-col="[{ size: '48px', type: 'rect' }]" />
|
|||
|
|
<wd-skeleton :loading="showContent" animation="flashed" theme="paragraph" :custom-style="{ width: '100%', marginLeft: '12px' }" :row-col="[{ width: '50%' }, { width: '100%' }]" />
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
</view>
|
|||
|
|
<view style="margin-top: 100px">
|
|||
|
|
<wd-status-tip image-size="100" v-if="categories.length === 0 && !showContent" tip="未选择门店或未上架商品" >
|
|||
|
|
<template #image>
|
|||
|
|
<wd-icon name="error-circle" color="#F56C6C" size="38"></wd-icon>
|
|||
|
|
</template>
|
|||
|
|
</wd-status-tip>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 编辑弹窗 -->
|
|||
|
|
<cart-edit v-model="showEdit" zIndex="999" :data="current" @done="reload" />
|
|||
|
|
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
|
|||
|
|
.wraper {
|
|||
|
|
display: flex;
|
|||
|
|
height: calc(100vh - 90px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.content {
|
|||
|
|
flex: 1;
|
|||
|
|
background: #fff;
|
|||
|
|
|
|||
|
|
.items{
|
|||
|
|
position: relative;
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12px;
|
|||
|
|
|
|||
|
|
.items-baseline{
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
|
|||
|
|
.price{
|
|||
|
|
color: #F56C6C;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cart-btn{
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
|
|||
|
|
.cart{
|
|||
|
|
background:rgb(254, 240, 240);
|
|||
|
|
padding: 1px 5px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-type {
|
|||
|
|
position: relative;
|
|||
|
|
height: 30px;
|
|||
|
|
line-height: 30px;
|
|||
|
|
padding: 0 8px 0 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-type::after {
|
|||
|
|
position: absolute;
|
|||
|
|
content: '';
|
|||
|
|
width: 1px;
|
|||
|
|
right: 0;
|
|||
|
|
top: 5px;
|
|||
|
|
bottom: 5px;
|
|||
|
|
background: rgba(0, 0, 0, 0.25);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.search-type {
|
|||
|
|
:deep(.icon-arrow) {
|
|||
|
|
display: inline-block;
|
|||
|
|
font-size: 20px;
|
|||
|
|
vertical-align: middle;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cart-icon{
|
|||
|
|
border: 1px solid #ccc;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
z-index: 9;
|
|||
|
|
padding: 12px 13px;
|
|||
|
|
background: #fff;
|
|||
|
|
}
|
|||
|
|
:deep(.custom-class-badge-add){
|
|||
|
|
.wd-badge__content{
|
|||
|
|
background: #fff;
|
|||
|
|
border: 1px solid #F56C6C;
|
|||
|
|
color: #F56C6C;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 门店信息展示样式 */
|
|||
|
|
.store-info {
|
|||
|
|
background: #ffffff;
|
|||
|
|
padding: 0 16px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
position: relative;
|
|||
|
|
min-height: 40px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.store-name {
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #333333;
|
|||
|
|
}
|
|||
|
|
</style>
|